From 4f0832b0e78538aa01bd4e438ec9676ddb7938c4 Mon Sep 17 00:00:00 2001 From: go-jet Date: Fri, 1 Nov 2024 12:30:37 +0100 Subject: [PATCH 1/2] Remove unused cast interface. --- mysql/cast.go | 58 ++++++--------------- postgres/cast.go | 131 ++++++++++++++++++----------------------------- sqlite/cast.go | 32 +++++------- 3 files changed, 79 insertions(+), 142 deletions(-) diff --git a/mysql/cast.go b/mysql/cast.go index ca647a5..dcf1f57 100644 --- a/mysql/cast.go +++ b/mysql/cast.go @@ -5,66 +5,40 @@ import ( "strconv" ) -type cast interface { - // AS casts expressions as castType type - AS(castType string) Expression - // AS_CHAR casts expression as char with optional length - AS_CHAR(length ...int) StringExpression - // AS_DATE casts expression AS date type - AS_DATE() DateExpression - // AS_FLOAT casts expressions as float type - AS_FLOAT() FloatExpression - // AS_DOUBLE casts expressions as double type - AS_DOUBLE() FloatExpression - // AS_DECIMAL casts expression AS numeric type - AS_DECIMAL() FloatExpression - // AS_TIME casts expression AS time type - AS_TIME() TimeExpression - // AS_DATETIME casts expression as datetime type - AS_DATETIME() DateTimeExpression - // AS_SIGNED casts expressions as signed integer type - AS_SIGNED() IntegerExpression - // AS_UNSIGNED casts expression as unsigned integer type - AS_UNSIGNED() IntegerExpression - // AS_BINARY casts expression as binary type - AS_BINARY() StringExpression -} - -type castImpl struct { +type cast struct { jet.Cast } // CAST function converts a expr (of any type) into latter specified datatype. -func CAST(expr Expression) cast { - castImpl := &castImpl{} +func CAST(expr Expression) *cast { + ret := &cast{} + ret.Cast = jet.NewCastImpl(expr) - castImpl.Cast = jet.NewCastImpl(expr) - - return castImpl + return ret } // AS casts expressions to castType -func (c *castImpl) AS(castType string) Expression { +func (c *cast) AS(castType string) Expression { return c.Cast.AS(castType) } // AS_DATETIME cast expression to DATETIME type -func (c *castImpl) AS_DATETIME() DateTimeExpression { +func (c *cast) AS_DATETIME() DateTimeExpression { return DateTimeExp(c.AS("DATETIME")) } // AS_SIGNED casts expression to SIGNED type -func (c *castImpl) AS_SIGNED() IntegerExpression { +func (c *cast) AS_SIGNED() IntegerExpression { return IntExp(c.AS("SIGNED")) } // AS_UNSIGNED casts expression to UNSIGNED type -func (c *castImpl) AS_UNSIGNED() IntegerExpression { +func (c *cast) AS_UNSIGNED() IntegerExpression { return IntExp(c.AS("UNSIGNED")) } // AS_CHAR casts expression to CHAR type with optional length -func (c *castImpl) AS_CHAR(length ...int) StringExpression { +func (c *cast) AS_CHAR(length ...int) StringExpression { if len(length) > 0 { return StringExp(c.AS("CHAR(" + strconv.Itoa(length[0]) + ")")) } @@ -73,29 +47,29 @@ func (c *castImpl) AS_CHAR(length ...int) StringExpression { } // AS_DATE casts expression AS DATE type -func (c *castImpl) AS_DATE() DateExpression { +func (c *cast) AS_DATE() DateExpression { return DateExp(c.AS("DATE")) } -func (c *castImpl) AS_FLOAT() FloatExpression { +func (c *cast) AS_FLOAT() FloatExpression { return FloatExp(c.AS("FLOAT")) } -func (c *castImpl) AS_DOUBLE() FloatExpression { +func (c *cast) AS_DOUBLE() FloatExpression { return FloatExp(c.AS("DOUBLE")) } // AS_DECIMAL casts expression AS DECIMAL type -func (c *castImpl) AS_DECIMAL() FloatExpression { +func (c *cast) AS_DECIMAL() FloatExpression { return FloatExp(c.AS("DECIMAL")) } // AS_TIME casts expression AS TIME type -func (c *castImpl) AS_TIME() TimeExpression { +func (c *cast) AS_TIME() TimeExpression { return TimeExp(c.AS("TIME")) } // AS_BINARY casts expression as BINARY type -func (c *castImpl) AS_BINARY() StringExpression { +func (c *cast) AS_BINARY() StringExpression { return StringExp(c.AS("BINARY")) } diff --git a/postgres/cast.go b/postgres/cast.go index e3a17a4..935cb08 100644 --- a/postgres/cast.go +++ b/postgres/cast.go @@ -7,84 +7,45 @@ import ( "github.com/go-jet/jet/v2/internal/jet" ) -type cast interface { - AS(castType string) Expression - // Cast expression AS bool type - AS_BOOL() BoolExpression - // Cast expression AS smallint type - AS_SMALLINT() IntegerExpression - // Cast expression AS integer type - AS_INTEGER() IntegerExpression - // Cast expression AS bigint type - AS_BIGINT() IntegerExpression - // Cast expression AS numeric type, using precision and optionally scale - AS_NUMERIC(precisionAndScale ...int) FloatExpression - // Cast expression AS real type - AS_REAL() FloatExpression - // Cast expression AS double precision type - AS_DOUBLE() FloatExpression - // Cast expression AS char with optional length - AS_CHAR(length ...int) StringExpression - // Cast expression AS date type - AS_DATE() DateExpression - // Cast expression AS numeric type, using precision and optionally scale - AS_DECIMAL() FloatExpression - // Cast expression AS time type - AS_TIME() TimeExpression - // Cast expression AS text type - AS_TEXT() StringExpression - // Cast expression AS bytea type - AS_BYTEA() StringExpression - // Cast expression AS time with time timezone type - AS_TIMEZ() TimezExpression - // Cast expression AS timestamp type - AS_TIMESTAMP() TimestampExpression - // Cast expression AS timestamp with timezone type - AS_TIMESTAMPZ() TimestampzExpression - // Cast expression AS interval type - AS_INTERVAL() IntervalExpression -} - -type castImpl struct { +type cast struct { jet.Cast } -// CAST function converts a expr (of any type) into latter specified datatype. -func CAST(expr Expression) cast { - castImpl := &castImpl{} +// CAST function converts an expr (of any type) into later specified datatype. +func CAST(expr Expression) *cast { + ret := &cast{} + ret.Cast = jet.NewCastImpl(expr) - castImpl.Cast = jet.NewCastImpl(expr) - - return castImpl + return ret } -// Cast expression as castType -func (b *castImpl) AS(castType string) Expression { +// AS casts expression as castType +func (b *cast) AS(castType string) Expression { return b.Cast.AS(castType) } -// Cast expression as bool type -func (b *castImpl) AS_BOOL() BoolExpression { +// AS_BOOL casts expression as bool type +func (b *cast) AS_BOOL() BoolExpression { return BoolExp(b.AS("boolean")) } -// Cast expression as smallint type -func (b *castImpl) AS_SMALLINT() IntegerExpression { +// AS_SMALLINT casts expression as smallint type +func (b *cast) AS_SMALLINT() IntegerExpression { return IntExp(b.AS("smallint")) } -// Cast expression AS integer type -func (b *castImpl) AS_INTEGER() IntegerExpression { +// AS_INTEGER casts expression AS integer type +func (b *cast) AS_INTEGER() IntegerExpression { return IntExp(b.AS("integer")) } -// Cast expression AS bigint type -func (b *castImpl) AS_BIGINT() IntegerExpression { +// AS_BIGINT casts expression AS bigint type +func (b *cast) AS_BIGINT() IntegerExpression { return IntExp(b.AS("bigint")) } -// Cast expression AS numeric type, using precision and optionally scale -func (b *castImpl) AS_NUMERIC(precisionAndScale ...int) FloatExpression { +// AS_NUMERIC casts expression as numeric type, using precision and optionally scale +func (b *cast) AS_NUMERIC(precisionAndScale ...int) FloatExpression { var castArgs string var argLen = len(precisionAndScale) @@ -97,22 +58,23 @@ func (b *castImpl) AS_NUMERIC(precisionAndScale ...int) FloatExpression { return FloatExp(b.AS("numeric" + castArgs)) } -// Cast expression AS real type -func (b *castImpl) AS_REAL() FloatExpression { +// AS_REAL casts expression AS real type +func (b *cast) AS_REAL() FloatExpression { return FloatExp(b.AS("real")) } -// Cast expression AS double precision type -func (b *castImpl) AS_DOUBLE() FloatExpression { +// AS_DOUBLE casts expression AS double precision type +func (b *cast) AS_DOUBLE() FloatExpression { return FloatExp(b.AS("double precision")) } -// Cast expression AS text type -func (b *castImpl) AS_TEXT() StringExpression { +// AS_TEXT casts expression AS text type +func (b *cast) AS_TEXT() StringExpression { return StringExp(b.AS("text")) } -func (b *castImpl) AS_CHAR(length ...int) StringExpression { +// AS_CHAR casts expression AS a character type +func (b *cast) AS_CHAR(length ...int) StringExpression { if len(length) > 0 { return StringExp(b.AS("char(" + strconv.Itoa(length[0]) + ")")) } @@ -120,42 +82,51 @@ func (b *castImpl) AS_CHAR(length ...int) StringExpression { return StringExp(b.AS("char")) } -// Cast expression AS date type -func (b *castImpl) AS_DATE() DateExpression { +// AS_VARCHAR casts expression AS a character varying type +func (b *cast) AS_VARCHAR(length ...int) StringExpression { + if len(length) > 0 { + return StringExp(b.AS("varchar(" + strconv.Itoa(length[0]) + ")")) + } + + return StringExp(b.AS("varchar")) +} + +// AS_DATE casts expression AS date type +func (b *cast) AS_DATE() DateExpression { return DateExp(b.AS("date")) } -// Cast expression AS date type -func (b *castImpl) AS_DECIMAL() FloatExpression { +// AS_DECIMAL casts expression AS date type +func (b *cast) AS_DECIMAL() FloatExpression { return FloatExp(b.AS("decimal")) } -// Cast expression AS text type -func (b *castImpl) AS_BYTEA() StringExpression { +// AS_BYTEA casts expression AS text type +func (b *cast) AS_BYTEA() StringExpression { return StringExp(b.AS("bytea")) } -// Cast expression AS date type -func (b *castImpl) AS_TIME() TimeExpression { +// AS_TIME casts expression AS date type +func (b *cast) AS_TIME() TimeExpression { return TimeExp(b.AS("time without time zone")) } -// Cast expression AS time with time timezone type -func (b *castImpl) AS_TIMEZ() TimezExpression { +// AS_TIMEZ casts expression AS time with time timezone type +func (b *cast) AS_TIMEZ() TimezExpression { return TimezExp(b.AS("time with time zone")) } -// Cast expression AS timestamp type -func (b *castImpl) AS_TIMESTAMP() TimestampExpression { +// AS_TIMESTAMP casts expression AS timestamp type +func (b *cast) AS_TIMESTAMP() TimestampExpression { return TimestampExp(b.AS("timestamp without time zone")) } -// Cast expression AS timestamp with timezone type -func (b *castImpl) AS_TIMESTAMPZ() TimestampzExpression { +// AS_TIMESTAMPZ casts expression AS timestamp with timezone type +func (b *cast) AS_TIMESTAMPZ() TimestampzExpression { return TimestampzExp(b.AS("timestamp with time zone")) } -// Cast expression AS interval type -func (b *castImpl) AS_INTERVAL() IntervalExpression { +// AS_INTERVAL casts expression AS interval type +func (b *cast) AS_INTERVAL() IntervalExpression { return IntervalExp(b.AS("interval")) } diff --git a/sqlite/cast.go b/sqlite/cast.go index 517fb95..0f68f43 100644 --- a/sqlite/cast.go +++ b/sqlite/cast.go @@ -4,52 +4,44 @@ import ( "github.com/go-jet/jet/v2/internal/jet" ) -type cast interface { - AS(castType string) Expression - AS_TEXT() StringExpression - AS_NUMERIC() FloatExpression - AS_INTEGER() IntegerExpression - AS_REAL() FloatExpression - AS_BLOB() StringExpression -} - -type castImpl struct { +type cast struct { jet.Cast } // CAST function converts a expr (of any type) into latter specified datatype. -func CAST(expr Expression) cast { - castImpl := &castImpl{} - castImpl.Cast = jet.NewCastImpl(expr) - return castImpl +func CAST(expr Expression) *cast { + ret := &cast{} + ret.Cast = jet.NewCastImpl(expr) + + return ret } // AS casts expressions to castType -func (c *castImpl) AS(castType string) Expression { +func (c *cast) AS(castType string) Expression { return c.Cast.AS(castType) } // AS_TEXT cast expression to TEXT type -func (c *castImpl) AS_TEXT() StringExpression { +func (c *cast) AS_TEXT() StringExpression { return StringExp(c.AS("TEXT")) } // AS_NUMERIC cast expression to NUMERIC type -func (c *castImpl) AS_NUMERIC() FloatExpression { +func (c *cast) AS_NUMERIC() FloatExpression { return FloatExp(c.AS("NUMERIC")) } // AS_INTEGER cast expression to INTEGER type -func (c *castImpl) AS_INTEGER() IntegerExpression { +func (c *cast) AS_INTEGER() IntegerExpression { return IntExp(c.AS("INTEGER")) } // AS_REAL cast expression to REAL type -func (c *castImpl) AS_REAL() FloatExpression { +func (c *cast) AS_REAL() FloatExpression { return FloatExp(c.AS("REAL")) } // AS_BLOB cast expression to BLOB type -func (c *castImpl) AS_BLOB() StringExpression { +func (c *cast) AS_BLOB() StringExpression { return StringExp(c.AS("BLOB")) } From 2183af42f426ba0f27f529dbab50b3c4a52946ef Mon Sep 17 00:00:00 2001 From: go-jet Date: Fri, 1 Nov 2024 12:34:46 +0100 Subject: [PATCH 2/2] Add PostgreSQL-specific character type constructors: Text, Char, and VarChar. --- README.md | 70 ++++++++++++++--------------- examples/quick-start/quick-start.go | 4 +- go.mod | 8 ++-- go.sum | 4 +- postgres/literal.go | 67 ++++++++++++++++----------- postgres/literal_test.go | 29 ++++-------- postgres/values.go | 4 +- tests/postgres/alltypes_test.go | 38 ++++++++-------- tests/postgres/select_test.go | 48 ++++++++++---------- tests/postgres/values_test.go | 24 +++++----- 10 files changed, 152 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index c84dd4e..3d5f57d 100644 --- a/README.md +++ b/README.md @@ -175,9 +175,9 @@ stmt := SELECT( INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)). INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)), ).WHERE( - Language.Name.EQ(String("English")). - AND(Category.Name.NOT_EQ(String("Action"))). - AND(Film.Length.GT(Int(180))), + Language.Name.EQ(Char(20)("English")). + AND(Category.Name.NOT_EQ(Text("Action"))). + AND(Film.Length.GT(Int32(180))), ).ORDER_BY( Actor.ActorID.ASC(), Film.FilmID.ASC(), @@ -200,35 +200,35 @@ args - query parameters ```sql SELECT actor.actor_id AS "actor.actor_id", - actor.first_name AS "actor.first_name", - actor.last_name AS "actor.last_name", - actor.last_update AS "actor.last_update", - film.film_id AS "film.film_id", - film.title AS "film.title", - film.description AS "film.description", - film.release_year AS "film.release_year", - film.language_id AS "film.language_id", - film.rental_duration AS "film.rental_duration", - film.rental_rate AS "film.rental_rate", - film.length AS "film.length", - film.replacement_cost AS "film.replacement_cost", - film.rating AS "film.rating", - film.last_update AS "film.last_update", - film.special_features AS "film.special_features", - film.fulltext AS "film.fulltext", - language.language_id AS "language.language_id", - language.name AS "language.name", - language.last_update AS "language.last_update", - category.category_id AS "category.category_id", - category.name AS "category.name", - category.last_update AS "category.last_update" + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update", + film.film_id AS "film.film_id", + film.title AS "film.title", + film.description AS "film.description", + film.release_year AS "film.release_year", + film.language_id AS "film.language_id", + film.rental_duration AS "film.rental_duration", + film.rental_rate AS "film.rental_rate", + film.length AS "film.length", + film.replacement_cost AS "film.replacement_cost", + film.rating AS "film.rating", + film.last_update AS "film.last_update", + film.special_features AS "film.special_features", + film.fulltext AS "film.fulltext", + language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update", + category.category_id AS "category.category_id", + category.name AS "category.name", + category.last_update AS "category.last_update" FROM dvds.actor - INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.actor_id) - INNER JOIN dvds.film ON (film.film_id = film_actor.film_id) - INNER JOIN dvds.language ON (language.language_id = film.language_id) - INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id) - INNER JOIN dvds.category ON (category.category_id = film_category.category_id) -WHERE ((language.name = $1) AND (category.name != $2)) AND (film.length > $3) + INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.actor_id) + INNER JOIN dvds.film ON (film.film_id = film_actor.film_id) + INNER JOIN dvds.language ON (language.language_id = film.language_id) + INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id) + INNER JOIN dvds.category ON (category.category_id = film_category.category_id) +WHERE ((language.name = $1::char(20)) AND (category.name != $2::text)) AND (film.length > $3::integer) ORDER BY actor.actor_id ASC, film.film_id ASC; ``` ```sh @@ -277,7 +277,7 @@ FROM dvds.actor INNER JOIN dvds.language ON (language.language_id = film.language_id) INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id) INNER JOIN dvds.category ON (category.category_id = film_category.category_id) -WHERE ((language.name = 'English') AND (category.name != 'Action')) AND (film.length > 180) +WHERE ((language.name = 'English'::char(20)) AND (category.name != 'Action'::text)) AND (film.length > 180::integer) ORDER BY actor.actor_id ASC, film.film_id ASC; ``` @@ -545,18 +545,18 @@ The most expensive bugs are the one discovered on the production, and the least With automatically generated type safe SQL, not only queries are written faster but bugs are found sooner. Let's return to quick start example, and take closer look at a line: ```go -AND(Film.Length.GT(Int(180))), +AND(Film.Length.GT(Int32(180))), ``` Let's say someone changes column `length` to `duration` from `film` table. The next go build will fail at that line, and the bug will be caught at compile time. -Let's say someone changes the type of `length` column to some non integer type. Build will also fail at the same line +Let's say someone changes the type of `length` column to some non-integer type. Build will also fail at the same line because integer columns and expressions can be only compared to other integer columns and expressions. Build will also fail if someone removes `length` column from `film` table. `Film` field will be omitted from SQL Builder and Model types, next time `jet` generator is run. -Without Jet these bugs will have to be either caught by some test or by manual testing. +Without Jet these bugs will have to be either caught by tests or by manual testing. ## Dependencies At the moment Jet dependence only of: diff --git a/examples/quick-start/quick-start.go b/examples/quick-start/quick-start.go index dc84989..f420169 100644 --- a/examples/quick-start/quick-start.go +++ b/examples/quick-start/quick-start.go @@ -46,8 +46,8 @@ func main() { INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)). INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)), ).WHERE( - Language.Name.EQ(String("English")). - AND(Category.Name.NOT_EQ(String("Action"))). + Language.Name.EQ(Char(20)("English")). + AND(Category.Name.NOT_EQ(Text("Action"))). AND(Film.Length.GT(Int(180))), ).ORDER_BY( Actor.ActorID.ASC(), diff --git a/go.mod b/go.mod index cf29d08..a52dcf7 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,21 @@ module github.com/go-jet/jet/v2 -go 1.20 +go 1.21 +// used by jet generator require ( github.com/go-sql-driver/mysql v1.8.1 - github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/jackc/pgconn v1.14.3 - github.com/jackc/pgtype v1.14.3 + github.com/jackc/pgtype v1.14.4 github.com/jackc/pgx/v4 v4.18.3 github.com/lib/pq v1.10.9 github.com/mattn/go-sqlite3 v1.14.24 ) +// used in tests require ( + github.com/google/go-cmp v0.6.0 github.com/pkg/profile v1.7.0 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 diff --git a/go.sum b/go.sum index c04d912..72aa88a 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCM github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= -github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= +github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8= +github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= diff --git a/postgres/literal.go b/postgres/literal.go index 26b75d8..e23ee9c 100644 --- a/postgres/literal.go +++ b/postgres/literal.go @@ -34,47 +34,64 @@ func Int64(value int64) IntegerExpression { return CAST(jet.Int(value)).AS_BIGINT() } -// Uint8 is constructor for 8 bit unsigned integer expressions literals. -func Uint8(value uint8) IntegerExpression { - return CAST(jet.Uint8(value)).AS_SMALLINT() -} - -// Uint16 is constructor for 16 bit unsigned integer expressions literals. -func Uint16(value uint16) IntegerExpression { - return CAST(jet.Uint16(value)).AS_INTEGER() -} - -// Uint32 is constructor for 32 bit unsigned integer expressions literals. -func Uint32(value uint32) IntegerExpression { - return CAST(jet.Uint32(value)).AS_BIGINT() -} - -// Uint64 is constructor for 64 bit unsigned integer expressions literals. -func Uint64(value uint64) IntegerExpression { - return CAST(jet.Uint64(value)).AS_BIGINT() -} - // Float creates new float literal expression var Float = jet.Float -// Float32 is constructor for 32 bit float literals -func Float32(value float32) FloatExpression { +// Real is placeholder constructor for 32-bit float literals +func Real(value float32) FloatExpression { return CAST(jet.Literal(value)).AS_REAL() } -// Float64 is constructor for 64 bit float literals -func Float64(value float64) FloatExpression { +// Double is placeholder constructor for 64-bit float literals +func Double(value float64) FloatExpression { return CAST(jet.Literal(value)).AS_DOUBLE() } // Decimal creates new float literal expression var Decimal = jet.Decimal -// String creates new string literal expression +// String is a parameter constructor for the PostgreSQL text type. Using the `Text` constructor is +// generally preferable. +// +// WARNING: String always applies a `text` type cast, which can be problematic if a parameter is compared +// to a `character` column, as this may prevent index usage. In such cases, consider using the Char +// constructor instead. See also other PostgreSQL-specific constructors: Text, Char, and VarChar. func String(value string) StringExpression { return CAST(jet.String(value)).AS_TEXT() } +// Text is a parameter constructor for the PostgreSQL text type. This constructor also adds an +// explicit placeholder type cast to text in the generated query, such as `$3::text`. +// Example usage: +// +// Text("English") +func Text(value string) StringExpression { + return CAST(jet.Literal(value)).AS_TEXT() +} + +// Char is a parameter constructor for the PostgreSQL character type. This constructor also adds an +// explicit placeholder type cast to text in the generated query, such as `$3::char(30)`. +// Example usage: +// +// Char(20)("English") +func Char(length ...int) func(value string) StringExpression { + return func(value string) StringExpression { + return CAST(StringExp(jet.Literal(value))).AS_CHAR(length...) + } +} + +// VarChar is a parameter constructor for the PostgreSQL character varying type. This constructor +// also adds an explicit placeholder type cast to text in the generated query, such as `$3::varchar(30)`. +// Example usage: +// +// VarChar(20)("English") +// VarChar()("English") +func VarChar(length ...int) func(value string) StringExpression { + return func(value string) StringExpression { + return CAST(StringExp(jet.Literal(value))).AS_VARCHAR(length...) + } +} + // Json creates new json literal expression func Json(value interface{}) StringExpression { switch value.(type) { diff --git a/postgres/literal_test.go b/postgres/literal_test.go index 9fef055..a50ea8f 100644 --- a/postgres/literal_test.go +++ b/postgres/literal_test.go @@ -34,32 +34,21 @@ func TestInt64(t *testing.T) { assertSerialize(t, Int64(val), `$1::bigint`, val) } -func TestUint8(t *testing.T) { - val := uint8(math.MaxUint8) - assertSerialize(t, Uint8(val), `$1::smallint`, val) -} - -func TestUint16(t *testing.T) { - val := uint16(math.MaxUint16) - assertSerialize(t, Uint16(val), `$1::integer`, val) -} - -func TestUint32(t *testing.T) { - val := uint32(math.MaxUint32) - assertSerialize(t, Uint32(val), `$1::bigint`, val) -} - -func TestUint64(t *testing.T) { - val := uint64(math.MaxUint64) - assertSerialize(t, Uint64(val), `$1::bigint`, val) -} - func TestFloat(t *testing.T) { assertSerialize(t, Float(12.34), `$1`, float64(12.34)) + + assertSerialize(t, Real(12.34), `$1::real`, float32(12.34)) + assertSerialize(t, Double(12.34), `$1::double precision`, float64(12.34)) } func TestString(t *testing.T) { assertSerialize(t, String("Some text"), `$1::text`, "Some text") + + assertSerialize(t, Text("Some text"), `$1::text`, "Some text") + assertSerialize(t, Char(20)("John Doe"), `$1::char(20)`, "John Doe") + assertSerialize(t, Char()("John Doe"), `$1::char`, "John Doe") + assertSerialize(t, VarChar(20)("John Doe"), `$1::varchar(20)`, "John Doe") + assertSerialize(t, VarChar()("John Doe"), `$1::varchar`, "John Doe") } func TestBytea(t *testing.T) { diff --git a/postgres/values.go b/postgres/values.go index 28d17cd..8d38ed3 100644 --- a/postgres/values.go +++ b/postgres/values.go @@ -12,8 +12,8 @@ type values struct { // Example usage: // // VALUES( -// WRAP(Int32(204), Float32(1.21)), -// WRAP(Int32(207), Float32(1.02)), +// WRAP(Int32(204), Real(1.21)), +// WRAP(Int32(207), Real(1.02)), // ) func VALUES(rows ...RowExpression) values { return values{Values: jet.Values(rows)} diff --git a/tests/postgres/alltypes_test.go b/tests/postgres/alltypes_test.go index d6cdfce..6c3755a 100644 --- a/tests/postgres/alltypes_test.go +++ b/tests/postgres/alltypes_test.go @@ -462,15 +462,15 @@ func TestStringOperators(t *testing.T) { query := AllTypes.SELECT( AllTypes.Text.EQ(AllTypes.Char), - AllTypes.Text.EQ(String("Text")), + AllTypes.Text.EQ(Text("Text")), AllTypes.Text.NOT_EQ(AllTypes.VarCharPtr), - AllTypes.Text.NOT_EQ(String("Text")), + AllTypes.Text.NOT_EQ(Text("Text")), AllTypes.Text.GT(AllTypes.Text), - AllTypes.Text.GT(String("Text")), + AllTypes.Text.GT(Text("Text")), AllTypes.Text.GT_EQ(AllTypes.TextPtr), - AllTypes.Text.GT_EQ(String("Text")), + AllTypes.Text.GT_EQ(Text("Text")), AllTypes.Text.LT(AllTypes.Char), - AllTypes.Text.LT(String("Text")), + AllTypes.Text.LT(Text("Text")), AllTypes.Text.LT_EQ(AllTypes.VarChar), AllTypes.Text.LT_EQ(String("Text")), AllTypes.Text.BETWEEN(String("min"), String("max")), @@ -490,13 +490,13 @@ func TestStringOperators(t *testing.T) { OCTET_LENGTH(AllTypes.Text), OCTET_LENGTH(String("length")), LOWER(AllTypes.VarCharPtr), - LOWER(String("length")), + LOWER(Char(4)("length")), UPPER(AllTypes.Char), - UPPER(String("upper")), + UPPER(VarChar()("upper")), BTRIM(AllTypes.VarChar), - BTRIM(String("btrim")), + BTRIM(Char()("btrim")), BTRIM(AllTypes.VarChar, String("AA")), - BTRIM(String("btrim"), String("AA")), + BTRIM(VarChar(11)("btrim"), String("AA")), LTRIM(AllTypes.VarChar), LTRIM(String("ltrim")), LTRIM(AllTypes.VarChar, String("A")), @@ -533,7 +533,7 @@ func TestStringOperators(t *testing.T) { TO_HEX(AllTypes.IntegerPtr), ) - dest := []struct{}{} + var dest []struct{} err := query.Query(db, &dest) require.NoError(t, err) @@ -630,9 +630,9 @@ func TestFloatOperators(t *testing.T) { AllTypes.Numeric.BETWEEN(Float(1.34), AllTypes.Decimal).AS("between"), AllTypes.Numeric.NOT_BETWEEN(AllTypes.Decimal.MUL(Float(3)), Float(100.12)).AS("not_between"), - TRUNC(AllTypes.Decimal.ADD(AllTypes.Decimal), Uint8(2)).AS("add1"), + TRUNC(AllTypes.Decimal.ADD(AllTypes.Decimal), Int8(2)).AS("add1"), TRUNC(AllTypes.Decimal.ADD(Float(11.22)), Int8(2)).AS("add2"), - TRUNC(AllTypes.Decimal.SUB(AllTypes.DecimalPtr), Uint16(2)).AS("sub1"), + TRUNC(AllTypes.Decimal.SUB(AllTypes.DecimalPtr), Int32(2)).AS("sub1"), TRUNC(AllTypes.Decimal.SUB(Float(11.22)), Int16(2)).AS("sub2"), TRUNC(AllTypes.Decimal.MUL(AllTypes.DecimalPtr), Int16(2)).AS("mul1"), TRUNC(AllTypes.Decimal.MUL(Float(11.22)), Int32(2)).AS("mul2"), @@ -736,13 +736,13 @@ func TestIntegerOperators(t *testing.T) { AllTypes.Integer.BETWEEN(Int(11), Int(200)).AS("between"), AllTypes.Integer.NOT_BETWEEN(Int(66), Int(77)).AS("not_between"), AllTypes.BigInt.LT(AllTypes.BigIntPtr).AS("lt1"), - AllTypes.BigInt.LT(Uint8(65)).AS("lt2"), + AllTypes.BigInt.LT(Int16(65)).AS("lt2"), AllTypes.BigInt.LT_EQ(AllTypes.BigIntPtr).AS("lte1"), - AllTypes.BigInt.LT_EQ(Uint16(65)).AS("lte2"), + AllTypes.BigInt.LT_EQ(Int32(65)).AS("lte2"), AllTypes.BigInt.GT(AllTypes.BigIntPtr).AS("gt1"), - AllTypes.BigInt.GT(Uint32(65)).AS("gt2"), + AllTypes.BigInt.GT(Int64(65)).AS("gt2"), AllTypes.BigInt.GT_EQ(AllTypes.BigIntPtr).AS("gte1"), - AllTypes.BigInt.GT_EQ(Uint64(65)).AS("gte2"), + AllTypes.BigInt.GT_EQ(Int64(65)).AS("gte2"), AllTypes.BigInt.ADD(AllTypes.BigInt).AS("add1"), AllTypes.BigInt.ADD(Int(11)).AS("add2"), @@ -1111,8 +1111,8 @@ func TestRowExpression(t *testing.T) { nowAddHour := time.Now().Add(time.Hour) stmt := SELECT( - ROW(Int32(1), Float32(11.22), String("john")).AS("row"), - WRAP(Int64(1), Float64(11.22), String("john")).AS("wrap"), + ROW(Int32(1), Real(11.22), Text("john")).AS("row"), + WRAP(Int64(1), Double(11.22), VarChar(10)("john")).AS("wrap"), ROW(Bool(false), DateT(now)).EQ(ROW(Bool(true), DateT(now))), WRAP(Bool(false), DateT(now)).NOT_EQ(WRAP(Bool(true), DateT(now))), @@ -1131,7 +1131,7 @@ func TestRowExpression(t *testing.T) { testutils.AssertStatementSql(t, stmt, ` SELECT ROW($1::integer, $2::real, $3::text) AS "row", - ($4::bigint, $5::double precision, $6::text) AS "wrap", + ($4::bigint, $5::double precision, $6::varchar(10)) AS "wrap", ROW($7::boolean, $8::date) = ROW($9::boolean, $10::date), ($11::boolean, $12::date) != ($13::boolean, $14::date), ROW($15::time without time zone) IS DISTINCT FROM (row(NOW()::time)), diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index 8bc816e..6ecc198 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -660,8 +660,8 @@ func TestSelectExecution1(t *testing.T) { ). WHERE( OR( - City.City.EQ(String("London")), - City.City.EQ(String("York")), + City.City.EQ(Text("London")), + City.City.EQ(Text("York")), ), ). ORDER_BY( @@ -741,7 +741,7 @@ func TestSelectExecution2(t *testing.T) { Customer.CustomerID.AS("my_customer.id"), Customer.LastName.AS("my_customer.last_name"), ). - WHERE(City.City.EQ(String("London")).OR(City.City.EQ(String("York")))). + WHERE(City.City.EQ(VarChar()("London")).OR(City.City.EQ(VarChar()("York")))). ORDER_BY(City.CityID, Address.AddressID, Customer.CustomerID) testutils.AssertDebugStatementSql(t, stmt, ` @@ -754,7 +754,7 @@ SELECT city.city_id AS "my_city.id", FROM dvds.city INNER JOIN dvds.address ON (address.city_id = city.city_id) INNER JOIN dvds.customer ON (customer.address_id = address.address_id) -WHERE (city.city = 'London'::text) OR (city.city = 'York'::text) +WHERE (city.city = 'London'::varchar) OR (city.city = 'York'::varchar) ORDER BY city.city_id, address.address_id, customer.customer_id; `, "London", "York") @@ -798,7 +798,7 @@ func TestSelectExecution3(t *testing.T) { Address.AddressID.AS("address_id"), Address.Address.AS("address_line"), ). - WHERE(City.City.EQ(String("London")).OR(City.City.EQ(String("York")))). + WHERE(City.City.EQ(VarChar(20)("London")).OR(City.City.EQ(VarChar(20)("York")))). ORDER_BY(City.CityID, Address.AddressID, Customer.CustomerID) testutils.AssertDebugStatementSql(t, stmt, ` @@ -811,7 +811,7 @@ SELECT city.city_id AS "city_id", FROM dvds.city INNER JOIN dvds.address ON (address.city_id = city.city_id) INNER JOIN dvds.customer ON (customer.address_id = address.address_id) -WHERE (city.city = 'London'::text) OR (city.city = 'York'::text) +WHERE (city.city = 'London'::varchar(20)) OR (city.city = 'York'::varchar(20)) ORDER BY city.city_id, address.address_id, customer.customer_id; `, "London", "York") @@ -1866,7 +1866,7 @@ SELECT customer.customer_id AS "customer.customer_id", FROM dvds.payment INNER JOIN dvds.customer ON (customer.customer_id = payment.customer_id) GROUP BY customer.customer_id -HAVING SUM(payment.amount) > 125.6 +HAVING SUM(payment.amount) > 125::real ORDER BY customer.customer_id, SUM(payment.amount) ASC; ` query := SELECT( @@ -1885,14 +1885,14 @@ ORDER BY customer.customer_id, SUM(payment.amount) ASC; ).GROUP_BY( Customer.CustomerID, ).HAVING( - SUMf(Payment.Amount).GT(Float(125.6)), + SUMf(Payment.Amount).GT(Real(125)), ).ORDER_BY( Customer.CustomerID, SUMf(Payment.Amount).ASC(), ) //fmt.Println(query.DebugSql()) - testutils.AssertDebugStatementSql(t, query, expectedSQL, float64(125.6)) + testutils.AssertDebugStatementSql(t, query, expectedSQL, float32(125)) var dest []struct { model.Customer @@ -2378,14 +2378,14 @@ func TestSelectUnion(t *testing.T) { SELECT payment.payment_id AS "payment.payment_id", payment.amount AS "payment.amount" FROM dvds.payment - WHERE payment.amount <= 100 + WHERE payment.amount <= 100::double precision ) UNION ALL ( SELECT payment.payment_id AS "payment.payment_id", payment.amount AS "payment.amount" FROM dvds.payment - WHERE payment.amount >= 200 + WHERE payment.amount >= 200::double precision ) ORDER BY "payment.payment_id" ASC, "payment.amount" DESC LIMIT 10 @@ -2394,10 +2394,10 @@ OFFSET 20; query := UNION_ALL( Payment. SELECT(Payment.PaymentID.AS("payment.payment_id"), Payment.Amount). - WHERE(Payment.Amount.LT_EQ(Float(100))), + WHERE(Payment.Amount.LT_EQ(Double(100))), Payment. SELECT(Payment.PaymentID, Payment.Amount). - WHERE(Payment.Amount.GT_EQ(Float(200))), + WHERE(Payment.Amount.GT_EQ(Double(200))), ).ORDER_BY( IntegerColumn("payment.payment_id").ASC(), Payment.Amount.DESC(), @@ -2729,7 +2729,7 @@ FROM dvds.actor INNER JOIN dvds.language ON (language.language_id = film.language_id) INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id) INNER JOIN dvds.category ON (category.category_id = film_category.category_id) -WHERE ((language.name = 'English'::text) AND (category.name != 'Action'::text)) AND (film.length > 180) +WHERE ((language.name = 'English'::char(20)) AND (category.name != 'Action'::text)) AND (film.length > 180::integer) ORDER BY actor.actor_id ASC, film.film_id ASC; ` @@ -2746,15 +2746,15 @@ ORDER BY actor.actor_id ASC, film.film_id ASC; INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)). INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)), ).WHERE( - Language.Name.EQ(String("English")). // note that every column has type. - AND(Category.Name.NOT_EQ(String("Action"))). // String column Language.Name and Category.Name can be compared only with string expression - AND(Film.Length.GT(Int(180))), // Film.Length is integer column and can be compared only with integer expression + Language.Name.EQ(Char(20)("English")). // note that every column has type. + AND(Category.Name.NOT_EQ(Text("Action"))). // String column Language.Name and Category.Name can be compared only with string expression + AND(Film.Length.GT(Int32(180))), // Film.Length is integer column and can be compared only with integer expression ).ORDER_BY( Actor.ActorID.ASC(), Film.FilmID.ASC(), ) - testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "English", "Action", int64(180)) + testutils.AssertDebugStatementSql(t, stmt, expectedSQL, "English", "Action", int32(180)) var dest []struct { model.Actor @@ -3162,7 +3162,7 @@ func TestSelectLateral(t *testing.T) { ).FROM( Language, ).WHERE( - Language.Name.NOT_IN(String("spanish")). + Language.Name.NOT_IN(Char(20)("Spanish"), Char(20)("Catalan")). AND(Film.LanguageID.EQ(Language.LanguageID)), ), ).AS("films") @@ -3191,7 +3191,7 @@ FROM dvds.film language.name AS "language.name", language.last_update AS "language.last_update" FROM dvds.language - WHERE (language.name NOT IN ('spanish'::text)) AND (film.language_id = language.language_id) + WHERE (language.name NOT IN ('Spanish'::char(20), 'Catalan'::char(20))) AND (film.language_id = language.language_id) ) AS films WHERE film.film_id = 1 ORDER BY film.film_id @@ -3236,7 +3236,7 @@ FROM dvds.film, language.name AS "language.name", language.last_update AS "language.last_update" FROM dvds.language - WHERE (language.name NOT IN ('spanish'::text)) AND (film.language_id = language.language_id) + WHERE (language.name NOT IN ('Spanish'::char(20), 'Catalan'::char(20))) AND (film.language_id = language.language_id) ) AS films WHERE film.film_id = 1 ORDER BY film.film_id @@ -3787,9 +3787,9 @@ func TestSelectConditionalFunctions(t *testing.T) { Film.SELECT(Film.FilmID).WHERE(Film.RentalDuration.GT(Int(100))), ).AS("exists"), CASE(Film.Length.GT(Int(120))). - WHEN(Bool(true)).THEN(String("long film")). - ELSE(String("short film")).AS("case"), - COALESCE(Film.Description, String("none")).AS("coalesce"), + WHEN(Bool(true)).THEN(Text("long film")). + ELSE(Text("short film")).AS("case"), + COALESCE(Film.Description, Text("none")).AS("coalesce"), NULLIF(Film.ReleaseYear, Int(200)).AS("null_if"), GREATEST(Film.RentalDuration, Int(4), Int(5)).AS("greatest"), LEAST(Film.RentalDuration, Int(7), Int(6)).AS("least"), diff --git a/tests/postgres/values_test.go b/tests/postgres/values_test.go index ce26276..bdb631c 100644 --- a/tests/postgres/values_test.go +++ b/tests/postgres/values_test.go @@ -15,9 +15,9 @@ import ( func TestVALUES(t *testing.T) { values := VALUES( - WRAP(Int32(1), Int32(2), Float32(4.666), Bool(false), String("txt")), - WRAP(Int32(11).ADD(Int32(2)), Int32(22), Float32(33.222), Bool(true), String("png")), - WRAP(Int32(11), Int32(22), Float32(33.222), Bool(true), NULL), + WRAP(Int32(1), Int32(2), Real(4.666), Bool(false), String("txt")), + WRAP(Int32(11).ADD(Int32(2)), Int32(22), Real(33.222), Bool(true), String("png")), + WRAP(Int32(11), Int32(22), Real(33.222), Bool(true), NULL), ).AS("values_table") stmt := SELECT( @@ -86,11 +86,11 @@ func TestVALUES_Join(t *testing.T) { lastUpdate := Timestamp(2007, time.February, 11, 12, 0, 0) filmValues := VALUES( - WRAP(String("Chamber Italian"), Int64(117), Int32(2005), Float32(5.82), lastUpdate), - WRAP(String("Grosse Wonderful"), Int64(49), Int32(2004), Float32(6.242), lastUpdate.ADD(INTERVAL(1, HOUR))), - WRAP(String("Airport Pollock"), Int64(54), Int32(2001), Float32(7.22), NULL), - WRAP(String("Bright Encounters"), Int64(73), Int32(2002), Float32(8.25), NULL), - WRAP(String("Academy Dinosaur"), Int64(83), Int32(2010), Float32(9.22), lastUpdate.SUB(INTERVAL(2, MINUTE))), + WRAP(String("Chamber Italian"), Int64(117), Int32(2005), Real(5.82), lastUpdate), + WRAP(String("Grosse Wonderful"), Int64(49), Int32(2004), Real(6.242), lastUpdate.ADD(INTERVAL(1, HOUR))), + WRAP(String("Airport Pollock"), Int64(54), Int32(2001), Real(7.22), NULL), + WRAP(String("Bright Encounters"), Int64(73), Int32(2002), Real(8.25), NULL), + WRAP(String("Academy Dinosaur"), Int64(83), Int32(2010), Real(9.22), lastUpdate.SUB(INTERVAL(2, MINUTE))), ).AS("film_values", title, IntegerColumn("length"), releaseYear, rentalRate, TimestampColumn("update_date")) @@ -216,10 +216,10 @@ func TestVALUES_CTE_Update(t *testing.T) { stmt := WITH( paymentsToUpdate.AS( VALUES( - WRAP(Int32(20564), Float32(1.21)), - WRAP(Int32(20567), Float32(1.02)), - WRAP(Int32(20570), Float32(1.34)), - WRAP(Int32(20573), Float32(1.72)), + WRAP(Int32(20564), Real(1.21)), + WRAP(Int32(20567), Real(1.02)), + WRAP(Int32(20570), Real(1.34)), + WRAP(Int32(20573), Real(1.72)), ), ), )(