2020-08-06 20:58:47 +03:00
|
|
|
package postgres
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2020-08-23 13:42:15 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/kind"
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2020-08-23 13:42:15 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/errz"
|
2020-08-23 22:00:13 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/sqlmodel"
|
2020-08-06 20:58:47 +03:00
|
|
|
)
|
|
|
|
|
2020-08-23 13:42:15 +03:00
|
|
|
func dbTypeNameFromKind(knd kind.Kind) string {
|
2022-12-18 03:51:33 +03:00
|
|
|
switch knd { //nolint:exhaustive
|
2020-08-06 20:58:47 +03:00
|
|
|
default:
|
2023-04-02 22:49:45 +03:00
|
|
|
panic(fmt.Sprintf("unsupported datatype {%s}", knd))
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Unknown:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "TEXT"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Text:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "TEXT"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Int:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "BIGINT"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Float:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "DOUBLE PRECISION"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Decimal:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "DECIMAL"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Bool:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "BOOLEAN"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Datetime:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "TIMESTAMP"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Time:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "TIME"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Date:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "DATE"
|
2020-08-23 13:42:15 +03:00
|
|
|
case kind.Bytes:
|
2020-08-06 20:58:47 +03:00
|
|
|
return "BYTEA"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// createTblKindDefaults is a map of Kind to the value
|
|
|
|
// to use for a column's DEFAULT clause in a CREATE TABLE statement.
|
2022-12-18 03:51:33 +03:00
|
|
|
var createTblKindDefaults = map[kind.Kind]string{ //nolint:exhaustive
|
2020-08-23 13:42:15 +03:00
|
|
|
kind.Text: `DEFAULT ''`,
|
|
|
|
kind.Int: `DEFAULT 0`,
|
|
|
|
kind.Float: `DEFAULT 0`,
|
|
|
|
kind.Decimal: `DEFAULT 0`,
|
|
|
|
kind.Bool: `DEFAULT false`,
|
|
|
|
kind.Datetime: "DEFAULT 'epoch'::timestamp",
|
|
|
|
kind.Date: "DEFAULT 'epoch'::date",
|
|
|
|
kind.Time: "DEFAULT '00:00:00'::time",
|
|
|
|
kind.Bytes: "DEFAULT ''::bytea",
|
|
|
|
kind.Unknown: `DEFAULT ''`,
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// buildCreateTableStmt builds a CREATE TABLE statement from tblDef.
|
|
|
|
// The implementation is minimal: it does not honor PK, FK, etc.
|
|
|
|
func buildCreateTableStmt(tblDef *sqlmodel.TableDef) string {
|
|
|
|
sb := strings.Builder{}
|
|
|
|
sb.WriteString(`CREATE TABLE "`)
|
|
|
|
sb.WriteString(tblDef.Name)
|
|
|
|
sb.WriteString("\" (")
|
|
|
|
|
|
|
|
for i, colDef := range tblDef.Cols {
|
|
|
|
sb.WriteString("\n\"")
|
|
|
|
sb.WriteString(colDef.Name)
|
|
|
|
sb.WriteString("\" ")
|
|
|
|
sb.WriteString(dbTypeNameFromKind(colDef.Kind))
|
|
|
|
|
|
|
|
if colDef.NotNull {
|
|
|
|
sb.WriteRune(' ')
|
|
|
|
sb.WriteString(createTblKindDefaults[colDef.Kind])
|
|
|
|
sb.WriteString(" NOT NULL")
|
|
|
|
}
|
|
|
|
|
|
|
|
if i < len(tblDef.Cols)-1 {
|
|
|
|
sb.WriteRune(',')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sb.WriteString("\n)")
|
|
|
|
|
|
|
|
return sb.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildUpdateStmt builds an UPDATE statement string.
|
|
|
|
func buildUpdateStmt(tbl string, cols []string, where string) (string, error) {
|
|
|
|
if len(cols) == 0 {
|
|
|
|
return "", errz.Errorf("no columns provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
sb := strings.Builder{}
|
|
|
|
sb.WriteString(`UPDATE "`)
|
|
|
|
sb.WriteString(tbl)
|
|
|
|
sb.WriteString(`" SET "`)
|
|
|
|
sb.WriteString(strings.Join(cols, `" = ?, "`))
|
|
|
|
sb.WriteString(`" = ?`)
|
|
|
|
if where != "" {
|
|
|
|
sb.WriteString(" WHERE ")
|
|
|
|
sb.WriteString(where)
|
|
|
|
}
|
|
|
|
|
|
|
|
s := replacePlaceholders(sb.String())
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// replacePlaceholders replaces all instances of the question mark
|
|
|
|
// rune in input with $1, $2, $3 placeholders.
|
|
|
|
func replacePlaceholders(input string) string {
|
|
|
|
if input == "" {
|
|
|
|
return input
|
|
|
|
}
|
|
|
|
|
|
|
|
var sb strings.Builder
|
|
|
|
pCount := 1
|
|
|
|
var i int
|
|
|
|
for {
|
|
|
|
i = strings.IndexRune(input, '?')
|
|
|
|
if i == -1 {
|
|
|
|
sb.WriteString(input)
|
|
|
|
break
|
|
|
|
}
|
2023-04-01 11:38:32 +03:00
|
|
|
// Found a ?
|
|
|
|
sb.WriteString(input[0:i])
|
|
|
|
sb.WriteRune('$')
|
|
|
|
sb.WriteString(strconv.Itoa(pCount))
|
|
|
|
pCount++
|
|
|
|
if i == len(input)-1 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
input = input[i+1:]
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return sb.String()
|
|
|
|
}
|