From 3c6946eeea0c380fd364385d117d198da52e93fb Mon Sep 17 00:00:00 2001 From: danilov6083 Date: Fri, 6 Feb 2026 22:04:32 +0300 Subject: [PATCH] dialect/sql: removed redundant yql statements --- dialect/sql/builder.go | 348 ++++-------------------------- dialect/sql/builder_test.go | 412 ------------------------------------ 2 files changed, 44 insertions(+), 716 deletions(-) diff --git a/dialect/sql/builder.go b/dialect/sql/builder.go index 0a3264e468..29b514e310 100644 --- a/dialect/sql/builder.go +++ b/dialect/sql/builder.go @@ -157,8 +157,7 @@ type InsertBuilder struct { conflict *conflict // YDB-specific: - isUpsert bool // use UPSERT instead of INSERT - isReplace bool // use REPLACE instead of INSERT + isUpsert bool // use UPSERT instead of INSERT } // Insert creates a builder for the `INSERT INTO` statement. @@ -186,21 +185,6 @@ func Upsert(table string) *InsertBuilder { return &InsertBuilder{table: table, isUpsert: true} } -// Replace creates a builder for the `REPLACE INTO` statement. -// REPLACE overwrites entire rows based on primary key comparison. -// For existing rows, the entire row is replaced (unspecified columns get default values). -// For missing rows, new rows are inserted. -// -// Replace("users"). -// Columns("id", "name", "age"). -// Values(1, "a8m", 10). -// Values(2, "foo", 20) -// -// Note: REPLACE is only supported in YDB dialect. -func Replace(table string) *InsertBuilder { - return &InsertBuilder{table: table, isReplace: true} -} - // Schema sets the database name for the insert table. func (i *InsertBuilder) Schema(name string) *InsertBuilder { i.schema = name @@ -494,20 +478,13 @@ func (i *InsertBuilder) Query() (string, []any) { func (i *InsertBuilder) QueryErr() (string, []any, error) { b := i.Builder.clone() - switch { - case i.isUpsert: + if i.isUpsert { if !b.ydb() { b.AddError(fmt.Errorf("UPSERT INTO is not supported by %q", b.dialect)) return "", nil, b.Err() } b.WriteString("UPSERT INTO ") - case i.isReplace: - if !b.ydb() { - b.AddError(fmt.Errorf("REPLACE INTO is not supported by %q", b.dialect)) - return "", nil, b.Err() - } - b.WriteString("REPLACE INTO ") - default: + } else { b.WriteString("INSERT INTO ") } @@ -597,10 +574,6 @@ type UpdateBuilder struct { order []any limit *int prefix Queries - - // YDB-specific: - onSelect *Selector // UPDATE ON subquery - isBatch bool // use BATCH UPDATE instead of UPDATE } // Update creates a builder for the `UPDATE` statement. @@ -608,17 +581,6 @@ type UpdateBuilder struct { // Update("users").Set("name", "foo").Set("age", 10) func Update(table string) *UpdateBuilder { return &UpdateBuilder{table: table} } -// BatchUpdate creates a builder for the `BATCH UPDATE` statement (YDB-specific). -// BATCH UPDATE processes large tables in batches, minimizing lock invalidation risk. -// -// Note: BATCH UPDATE is only supported in YDB dialect. -// Note: YDB uses path notation for tables, e.g. "/database/path/to/table" -// -// BatchUpdate("/local/my_table").Set("status", "active").Where(GT("id", 100)) -func BatchUpdate(table string) *UpdateBuilder { - return &UpdateBuilder{table: table, isBatch: true} -} - // Schema sets the database name for the updated table. func (u *UpdateBuilder) Schema(name string) *UpdateBuilder { u.schema = name @@ -721,25 +683,6 @@ func (u *UpdateBuilder) Returning(columns ...string) *UpdateBuilder { return u } -// On sets a subquery for the UPDATE ON statement (YDB-specific). -// The subquery must return columns that include all primary key columns. -// For each row in the subquery result, the corresponding row in the table is updated. -// -// Update("users"). -// On( -// Select("key", "name") -// .From(Table("temp_updates")) -// .Where(EQ("status", "pending")) -// ) -func (u *UpdateBuilder) On(s *Selector) *UpdateBuilder { - if u.ydb() { - u.onSelect = s - } else { - u.AddError(fmt.Errorf("UPDATE ON is not supported by %q", u.dialect)) - } - return u -} - // Query returns query representation of an `UPDATE` statement. func (u *UpdateBuilder) Query() (string, []any) { query, args, _ := u.QueryErr() @@ -753,36 +696,10 @@ func (u *UpdateBuilder) QueryErr() (string, []any, error) { b.Pad() } - // BATCH UPDATE (YDB-specific) - if u.isBatch { - if !b.ydb() { - b.AddError(fmt.Errorf("BATCH UPDATE is not supported by %q", b.dialect)) - return "", nil, b.Err() - } - if len(u.returning) > 0 { - b.AddError(fmt.Errorf("BATCH UPDATE: RETURNING clause is not supported")) - return "", nil, b.Err() - } - if u.onSelect != nil { - b.AddError(fmt.Errorf("BATCH UPDATE: UPDATE ON pattern is not supported")) - return "", nil, b.Err() - } - b.WriteString("BATCH UPDATE ") - } else { - b.WriteString("UPDATE ") - } - + b.WriteString("UPDATE ") b.writeSchema(u.schema) b.Ident(u.table) - // UPDATE ON pattern (YDB-specific) - if u.onSelect != nil { - b.WriteString(" ON ") - b.Join(u.onSelect) - joinReturning(u.returning, &b) - return b.String(), b.args, nil - } - // Standard UPDATE SET pattern b.WriteString(" SET ") u.writeSetter(&b) @@ -827,14 +744,10 @@ func (u *UpdateBuilder) writeSetter(b *Builder) { // DeleteBuilder is a builder for `DELETE` statement. type DeleteBuilder struct { Builder - table string - schema string - where *Predicate - - // YDB-specific: - onSelect *Selector // DELETE FROM ... ON SELECT pattern - returning []string - isBatch bool // use BATCH DELETE instead of DELETE + table string + schema string + where *Predicate + returning []string // YDB-specific } // Delete creates a builder for the `DELETE` statement. @@ -852,16 +765,6 @@ type DeleteBuilder struct { // ) func Delete(table string) *DeleteBuilder { return &DeleteBuilder{table: table} } -// BatchDelete creates a builder for the `BATCH DELETE FROM` statement (YDB-specific). -// BATCH DELETE processes large tables in batches, minimizing lock invalidation risk. -// -// Note: BATCH DELETE is only supported in YDB dialect. -// -// BatchDelete("/local/my_table").Where(GT("Key1", 1)) -func BatchDelete(table string) *DeleteBuilder { - return &DeleteBuilder{table: table, isBatch: true} -} - // Schema sets the database name for the table whose row will be deleted. func (d *DeleteBuilder) Schema(name string) *DeleteBuilder { d.schema = name @@ -887,20 +790,6 @@ func (d *DeleteBuilder) FromSelect(s *Selector) *DeleteBuilder { return d } -// On sets the subquery for DELETE FROM ... ON SELECT pattern (YDB-specific). -// This allows deleting rows based on a subquery that returns primary key columns. -// -// Delete("users"). -// On(Select("id").From(Table("users")).Where(EQ("status", "inactive"))) -func (d *DeleteBuilder) On(s *Selector) *DeleteBuilder { - if d.ydb() { - d.onSelect = s - } else { - d.AddError(fmt.Errorf("DELETE ON is not supported by %q", d.dialect)) - } - return d -} - // Returning adds the `RETURNING` clause to the delete statement. // Supported by YDB. func (d *DeleteBuilder) Returning(columns ...string) *DeleteBuilder { @@ -921,34 +810,11 @@ func (d *DeleteBuilder) Query() (string, []any) { func (d *DeleteBuilder) QueryErr() (string, []any, error) { b := d.Builder.clone() - // BATCH DELETE (YDB-specific) - if d.isBatch { - if !b.ydb() { - b.AddError(fmt.Errorf("BATCH DELETE is not supported by %q", b.dialect)) - return "", nil, b.Err() - } - if len(d.returning) > 0 { - b.AddError(fmt.Errorf("BATCH DELETE: RETURNING clause is not supported")) - return "", nil, b.Err() - } - if d.onSelect != nil { - b.AddError(fmt.Errorf("BATCH DELETE: DELETE ON pattern is not supported")) - return "", nil, b.Err() - } - b.WriteString("BATCH DELETE FROM ") - } else { - b.WriteString("DELETE FROM ") - } - + b.WriteString("DELETE FROM ") b.writeSchema(d.schema) b.Ident(d.table) - // YDB-specific DELETE ON SELECT pattern - if d.onSelect != nil { - d.onSelect.SetDialect(b.dialect) - b.WriteString(" ON ") - b.Join(d.onSelect) - } else if d.where != nil { + if d.where != nil { b.WriteString(" WHERE ") b.Join(d.where) } @@ -1887,8 +1753,7 @@ type SelectTable struct { quote bool // YDB-specific: - index string // secondary index name for VIEW clause - isCte bool // YDB-specific: marks this as a CTE reference + isCte bool // YDB-specific: marks this as a CTE reference } // Table returns a new table selector. @@ -1911,20 +1776,6 @@ func (s *SelectTable) As(alias string) *SelectTable { return s } -// View sets the secondary index name for the VIEW clause (YDB-specific). -// This allows explicit use of secondary indexes in SELECT and JOIN operations. -// -// t := Table("users").View("idx_email").As("u") -// Select().From(Table("orders")).Join(t).On(...) -func (s *SelectTable) View(index string) *SelectTable { - if s.ydb() { - s.index = index - } else { - s.AddError(fmt.Errorf("VIEW is not supported by %q", s.dialect)) - } - return s -} - // C returns a formatted string for the table column. func (s *SelectTable) C(column string) string { name := s.name @@ -1982,12 +1833,6 @@ func (s *SelectTable) ref() string { b.Ident(s.name) - // YDB-specific: VIEW clause for secondary indexes - if s.index != "" { - b.WriteString(" VIEW ") - b.Ident(s.index) - } - if s.as != "" { b.WriteString(" AS ") b.Ident(s.as) @@ -2019,25 +1864,24 @@ type Selector struct { Builder // ctx stores contextual data typically from // generated code such as alternate table schemas. - ctx context.Context - as string - selection []*selection - from []TableView - joins []join - collected [][]*Predicate - where *Predicate - or bool - not bool - order []any - assumeOrder []string // YDB-specific: ASSUME ORDER BY columns - group []string - having *Predicate - limit *int - offset *int - distinct bool - setOps []setOp - prefix Queries - lock *LockOptions + ctx context.Context + as string + selection []*selection + from []TableView + joins []join + collected [][]*Predicate + where *Predicate + or bool + not bool + order []any + group []string + having *Predicate + limit *int + offset *int + distinct bool + setOps []setOp + prefix Queries + lock *LockOptions } // New returns a new Selector with the same dialect and context. @@ -2456,36 +2300,11 @@ func (s *Selector) FullJoin(t TableView) *Selector { return s.join("FULL JOIN", t) } -// LeftSemiJoin appends a `LEFT SEMI JOIN` clause to the statement (YDB-specific). -func (s *Selector) LeftSemiJoin(t TableView) *Selector { - return s.join("LEFT SEMI JOIN", t) -} - -// RightSemiJoin appends a `RIGHT SEMI JOIN` clause to the statement (YDB-specific). -func (s *Selector) RightSemiJoin(t TableView) *Selector { - return s.join("RIGHT SEMI JOIN", t) -} - -// LeftOnlyJoin appends a `LEFT ONLY JOIN` clause to the statement (YDB-specific). -func (s *Selector) LeftOnlyJoin(t TableView) *Selector { - return s.join("LEFT ONLY JOIN", t) -} - -// RightOnlyJoin appends a `RIGHT ONLY JOIN` clause to the statement (YDB-specific). -func (s *Selector) RightOnlyJoin(t TableView) *Selector { - return s.join("RIGHT ONLY JOIN", t) -} - // CrossJoin appends a `CROSS JOIN` clause to the statement. func (s *Selector) CrossJoin(t TableView) *Selector { return s.join("CROSS JOIN", t) } -// ExclusionJoin appends an `EXCLUSION JOIN` clause to the statement (YDB-specific). -func (s *Selector) ExclusionJoin(t TableView) *Selector { - return s.join("EXCLUSION JOIN", t) -} - // join adds a join table to the selector with the given kind. func (s *Selector) join(kind string, t TableView) *Selector { s.joins = append(s.joins, join{ @@ -2784,22 +2603,21 @@ func (s *Selector) Clone() *Selector { joins[i] = s.joins[i].clone() } return &Selector{ - Builder: s.Builder.clone(), - ctx: s.ctx, - as: s.as, - or: s.or, - not: s.not, - from: s.from, - limit: s.limit, - offset: s.offset, - distinct: s.distinct, - where: s.where.clone(), - having: s.having.clone(), - joins: append([]join{}, joins...), - group: append([]string{}, s.group...), - order: append([]any{}, s.order...), - assumeOrder: append([]string{}, s.assumeOrder...), - selection: append([]*selection{}, s.selection...), + Builder: s.Builder.clone(), + ctx: s.ctx, + as: s.as, + or: s.or, + not: s.not, + from: s.from, + limit: s.limit, + offset: s.offset, + distinct: s.distinct, + where: s.where.clone(), + having: s.having.clone(), + joins: append([]join{}, joins...), + group: append([]string{}, s.group...), + order: append([]any{}, s.order...), + selection: append([]*selection{}, s.selection...), } } @@ -2827,11 +2645,6 @@ func DescExpr(x Querier) Querier { // OrderBy appends the `ORDER BY` clause to the `SELECT` statement. func (s *Selector) OrderBy(columns ...string) *Selector { - if s.ydb() && len(s.assumeOrder) != 0 { - s.AddError(fmt.Errorf("ORDER BY: can't be used with ASSUME ORDER BY simultaneously")) - return s - } - for i := range columns { s.order = append(s.order, columns[i]) } @@ -2873,27 +2686,6 @@ func (s *Selector) ClearOrder() *Selector { return s } -// AssumeOrderBy appends the `ASSUME ORDER BY` clause to the `SELECT` statement (YDB-specific). -// This tells YDB to assume the data is already sorted without actually sorting it. -// This is an optimization hint and only works with column names (not expressions). -// -// Select("*"). -// From(Table("users")). -// AssumeOrderBy("first-key", Desc("second-key")) -func (s *Selector) AssumeOrderBy(columns ...string) *Selector { - if s.ydb() { - if len(s.order) != 0 { - s.AddError(fmt.Errorf("ASSUME ORDER BY: can't be used with ORDER BY simultaneously")) - return s - } - - s.assumeOrder = append(s.assumeOrder, columns...) - } else { - s.AddError(fmt.Errorf("ASSUME ORDER BY is not supported by %q", s.dialect)) - } - return s -} - // GroupBy appends the `GROUP BY` clause to the `SELECT` statement. func (s *Selector) GroupBy(columns ...string) *Selector { s.group = append(s.group, columns...) @@ -2991,7 +2783,6 @@ func (s *Selector) Query() (string, []any) { s.applyAliasesToOrder() } joinOrder(s.order, &b) - s.joinAssumeOrder(&b) if s.limit != nil { b.WriteString(" LIMIT ") b.WriteString(strconv.Itoa(*s.limit)) @@ -3109,19 +2900,6 @@ func joinOrder(order []any, b *Builder) { } } -func (s *Selector) joinAssumeOrder(b *Builder) { - if !b.ydb() || len(s.assumeOrder) == 0 { - return - } - b.WriteString(" ASSUME ORDER BY ") - for i := range s.assumeOrder { - if i > 0 { - b.Comma() - } - b.Ident(s.assumeOrder[i]) - } -} - func joinReturning(columns []string, b *Builder) { supportedByDialect := b.postgres() || b.sqlite() || b.ydb() if len(columns) == 0 || !supportedByDialect { @@ -4185,19 +3963,6 @@ func (d *DialectBuilder) Upsert(table string) *InsertBuilder { return b } -// Replace creates an InsertBuilder for the REPLACE statement with the configured dialect. -// REPLACE is only supported in YDB dialect. -// -// Dialect(dialect.YDB). -// Replace("users"). -// Columns("id", "name", "age"). -// Values(1, "a8m", 10) -func (d *DialectBuilder) Replace(table string) *InsertBuilder { - b := Replace(table) - b.SetDialect(d.dialect) - return b -} - // Update creates a UpdateBuilder for the configured dialect. // // Dialect(dialect.Postgres). @@ -4208,19 +3973,6 @@ func (d *DialectBuilder) Update(table string) *UpdateBuilder { return b } -// BatchUpdate creates an UpdateBuilder for the BATCH UPDATE statement with the configured dialect. -// BATCH UPDATE is only supported in YDB dialect. -// -// Dialect(dialect.YDB). -// BatchUpdate("users"). -// Set("status", "active"). -// Where(GT("created_at", time.Now())) -func (d *DialectBuilder) BatchUpdate(table string) *UpdateBuilder { - b := BatchUpdate(table) - b.SetDialect(d.dialect) - return b -} - // Delete creates a DeleteBuilder for the configured dialect. // // Dialect(dialect.Postgres). @@ -4231,18 +3983,6 @@ func (d *DialectBuilder) Delete(table string) *DeleteBuilder { return b } -// BatchDelete creates a DeleteBuilder for the BATCH DELETE statement with the configured dialect. -// BATCH DELETE is only supported in YDB dialect. -// -// Dialect(dialect.YDB). -// BatchDelete("users"). -// Where(GT("Key1", 1)) -func (d *DialectBuilder) BatchDelete(table string) *DeleteBuilder { - b := BatchDelete(table) - b.SetDialect(d.dialect) - return b -} - // Select creates a Selector for the configured dialect. // // Dialect(dialect.Postgres). diff --git a/dialect/sql/builder_test.go b/dialect/sql/builder_test.go index a3ff15fc98..839f471a1d 100644 --- a/dialect/sql/builder_test.go +++ b/dialect/sql/builder_test.go @@ -1575,26 +1575,6 @@ AND "users"."id1" < "users"."id2") AND "users"."id1" <= "users"."id2"`, "\n", "" driver.NamedValue{Name: "p1", Value: "expired"}, }, }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - subquery := d.Select("id", "email"). - From(Table("users")). - Where(EQ("status", "ToDelete")) - return d.Delete("users").On(subquery) - }(), - wantQuery: "DELETE FROM `users` ON SELECT `id`, `email` FROM `users` WHERE `status` = $p0", - wantArgs: []any{driver.NamedValue{Name: "p0", Value: "ToDelete"}}, - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - subquery := d.Select("*"). - From(Table("temp_delete_list")) - return d.Delete("users").On(subquery) - }(), - wantQuery: "DELETE FROM `users` ON SELECT * FROM `temp_delete_list`", - }, { input: Dialect(dialect.YDB). Delete("orders"). @@ -1611,17 +1591,6 @@ AND "users"."id1" < "users"."id2") AND "users"."id1" <= "users"."id2"`, "\n", "" wantQuery: "DELETE FROM `orders` WHERE `status` = $p0 RETURNING `order_id`, `order_date`", wantArgs: []any{driver.NamedValue{Name: "p0", Value: "cancelled"}}, }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - subquery := d.Select("id"). - From(Table("temp_ids")) - return d.Delete("users"). - On(subquery). - Returning("id", "name") - }(), - wantQuery: "DELETE FROM `users` ON SELECT `id` FROM `temp_ids` RETURNING `id`, `name`", - }, { input: Dialect(dialect.YDB).Delete("users"), wantQuery: "DELETE FROM `users`", @@ -1734,47 +1703,6 @@ AND "users"."id1" < "users"."id2") AND "users"."id1" <= "users"."id2"`, "\n", "" driver.NamedValue{Name: "p2", Value: "john@example.com"}, }, }, - { - input: Dialect(dialect.YDB). - Replace("users"). - Columns("id", "name", "age"). - Values(1, "a8m", 10), - wantQuery: "REPLACE INTO `users` (`id`, `name`, `age`) VALUES ($p0, $p1, $p2)", - wantArgs: []any{ - driver.NamedValue{Name: "p0", Value: 1}, - driver.NamedValue{Name: "p1", Value: "a8m"}, - driver.NamedValue{Name: "p2", Value: 10}, - }, - }, - { - input: Dialect(dialect.YDB). - Replace("users"). - Columns("id", "name", "age"). - Values(1, "a8m", 10). - Values(2, "foo", 20), - wantQuery: "REPLACE INTO `users` (`id`, `name`, `age`) VALUES ($p0, $p1, $p2), ($p3, $p4, $p5)", - wantArgs: []any{ - driver.NamedValue{Name: "p0", Value: 1}, - driver.NamedValue{Name: "p1", Value: "a8m"}, - driver.NamedValue{Name: "p2", Value: 10}, - driver.NamedValue{Name: "p3", Value: 2}, - driver.NamedValue{Name: "p4", Value: "foo"}, - driver.NamedValue{Name: "p5", Value: 20}, - }, - }, - { - input: Dialect(dialect.YDB). - Replace("orders"). - Columns("order_id", "status", "amount"). - Values(1001, "shipped", 500). - Returning("*"), - wantQuery: "REPLACE INTO `orders` (`order_id`, `status`, `amount`) VALUES ($p0, $p1, $p2) RETURNING *", - wantArgs: []any{ - driver.NamedValue{Name: "p0", Value: 1001}, - driver.NamedValue{Name: "p1", Value: "shipped"}, - driver.NamedValue{Name: "p2", Value: 500}, - }, - }, { input: Dialect(dialect.YDB). Update("users"). @@ -1799,126 +1727,6 @@ AND "users"."id1" < "users"."id2") AND "users"."id1" <= "users"."id2"`, "\n", "" driver.NamedValue{Name: "p1", Value: "2023-01-01"}, }, }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - subquery := d.Select("key", "value"). - From(Table("temp_updates")). - Where(EQ("status", "pending")) - return d.Update("users").On(subquery) - }(), - wantQuery: "UPDATE `users` ON SELECT `key`, `value` FROM `temp_updates` WHERE `status` = $p0", - wantArgs: []any{driver.NamedValue{Name: "p0", Value: "pending"}}, - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - subquery := d.Select("*"). - From(Table("staged_data")) - return d.Update("users"). - On(subquery). - Returning("id", "name") - }(), - wantQuery: "UPDATE `users` ON SELECT * FROM `staged_data` RETURNING `id`, `name`", - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - t1 := d.Table("orders") - t2 := d.Table("users").View("idx_email").As("u") - return d.Select(t1.C("id"), t2.C("name")). - From(t1). - Join(t2). - On(t1.C("user_id"), t2.C("id")) - }(), - wantQuery: "SELECT `orders`.`id` AS `id`, `u`.`name` AS `name` FROM `orders` JOIN `users` VIEW `idx_email` AS `u` ON `orders`.`user_id` = `u`.`id`", - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - t1 := d.Table("a_table").As("a") - t2 := d.Table("b_table").View("b_index_ref").As("b") - return d.Select(t1.C("value"), t2.C("value")). - From(t1). - Join(t2). - On(t1.C("ref"), t2.C("ref")) - }(), - wantQuery: "SELECT `a`.`value` AS `value`, `b`.`value` AS `value` FROM `a_table` AS `a` JOIN `b_table` VIEW `b_index_ref` AS `b` ON `a`.`ref` = `b`.`ref`", - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - t1 := d.Table("orders") - t2 := d.Table("products").View("idx_category") - return d.Select("*"). - From(t1). - LeftJoin(t2). - On(t1.C("product_id"), t2.C("id")). - Where(EQ(t2.C("category"), "Electronics")) - }(), - wantQuery: "SELECT * FROM `orders` LEFT JOIN `products` VIEW `idx_category` AS `t1` ON `orders`.`product_id` = `t1`.`id` WHERE `t1`.`category` = $p0", - wantArgs: []any{driver.NamedValue{Name: "p0", Value: "Electronics"}}, - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - t1 := d.Table("users") - t2 := d.Table("blacklist") - return d.Select(t1.C("id"), t1.C("name")). - From(t1). - LeftSemiJoin(t2). - On(t1.C("id"), t2.C("user_id")) - }(), - wantQuery: "SELECT `users`.`id` AS `id`, `users`.`name` AS `name` FROM `users` LEFT SEMI JOIN `blacklist` AS `t1` ON `users`.`id` = `t1`.`user_id`", - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - t1 := d.Table("orders") - t2 := d.Table("active_users").As("t1") - return d.Select(t2.C("id"), t2.C("email")). - From(t1). - RightSemiJoin(t2). - On(t1.C("user_id"), t2.C("id")) - }(), - wantQuery: "SELECT `t1`.`id` AS `id`, `t1`.`email` AS `email` FROM `orders` RIGHT SEMI JOIN `active_users` AS `t1` ON `orders`.`user_id` = `t1`.`id`", - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - t1 := d.Table("users") - t2 := d.Table("deleted_users") - return d.Select(t1.C("id"), t1.C("name")). - From(t1). - LeftOnlyJoin(t2). - On(t1.C("id"), t2.C("id")) - }(), - wantQuery: "SELECT `users`.`id` AS `id`, `users`.`name` AS `name` FROM `users` LEFT ONLY JOIN `deleted_users` AS `t1` ON `users`.`id` = `t1`.`id`", - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - t1 := d.Table("archived") - t2 := d.Table("users").As("t1") - return d.Select(t2.C("id"), t2.C("status")). - From(t1). - RightOnlyJoin(t2). - On(t1.C("user_id"), t2.C("id")) - }(), - wantQuery: "SELECT `t1`.`id` AS `id`, `t1`.`status` AS `status` FROM `archived` RIGHT ONLY JOIN `users` AS `t1` ON `archived`.`user_id` = `t1`.`id`", - }, - { - input: func() Querier { - d := Dialect(dialect.YDB) - t1 := d.Table("table_a").As("a") - t2 := d.Table("table_b").As("b") - return d.Select(t1.C("key"), t2.C("key")). - From(t1). - ExclusionJoin(t2). - On(t1.C("key"), t2.C("key")) - }(), - wantQuery: "SELECT `a`.`key` AS `key`, `b`.`key` AS `key` FROM `table_a` AS `a` EXCLUSION JOIN `table_b` AS `b` ON `a`.`key` = `b`.`key`", - }, { input: func() Querier { t1 := Table("users").As("u") @@ -2231,226 +2039,6 @@ func TestSelector_Intersect(t *testing.T) { require.Equal(t, []any{20, "true", 18}, args) } -func TestSelector_AssumeOrderBy_YDB(t *testing.T) { - query, args := Dialect(dialect.YDB). - Select("*"). - From(Table("my_table")). - AssumeOrderBy("key"). - Query() - require.Equal(t, "SELECT * FROM `my_table` ASSUME ORDER BY `key`", query) - require.Empty(t, args) - - query, args = Dialect(dialect.YDB). - Select("key", "subkey"). - From(Table("my_table")). - AssumeOrderBy("key", Desc("subkey")). - Query() - require.Equal(t, "SELECT `key`, `subkey` FROM `my_table` ASSUME ORDER BY `key`, `subkey` DESC", query) - require.Empty(t, args) - - query, args = Dialect(dialect.YDB). - Select("*"). - From(Table("orders")). - Where(EQ("status", "active")). - AssumeOrderBy("created_at"). - Query() - require.Equal(t, "SELECT * FROM `orders` WHERE `status` = $p0 ASSUME ORDER BY `created_at`", query) - require.Equal(t, []any{driver.NamedValue{Name: "p0", Value: "active"}}, args) - - query, args = Dialect(dialect.YDB). - Select("id", "name"). - From(Table("users")). - AssumeOrderBy("id"). - Limit(10). - Query() - require.Equal(t, "SELECT `id`, `name` FROM `users` ASSUME ORDER BY `id` LIMIT 10", query) - require.Empty(t, args) - - t.Run("OrderBy then AssumeOrderBy should error", func(t *testing.T) { - selector := Dialect(dialect.YDB). - Select("*"). - From(Table("users")). - OrderBy("id"). - AssumeOrderBy("id") - - err := selector.Err() - require.Error(t, err) - }) - - t.Run("AssumeOrderBy then OrderBy should error", func(t *testing.T) { - selector := Dialect(dialect.YDB). - Select("*"). - From(Table("users")). - AssumeOrderBy("id"). - OrderBy("id") - - err := selector.Err() - require.Error(t, err) - }) - - t.Run("Non-YDB dialect with AssumeOrderBy should error", func(t *testing.T) { - selector := Dialect(dialect.Postgres). - Select("*"). - From(Table("users")). - AssumeOrderBy("name") - - err := selector.Err() - require.Error(t, err) - }) -} - -func TestSelector_VIEW_SecondaryIndex_YDB(t *testing.T) { - t.Run("Simple SELECT with VIEW in FROM clause", func(t *testing.T) { - d := Dialect(dialect.YDB) - query, args := d.Select("series_id", "title", "info", "release_date", "views", "uploaded_user_id"). - From(d.Table("series").View("views_index")). - Where(GTE("views", 1000)). - Query() - - require.Equal(t, "SELECT `series_id`, `title`, `info`, `release_date`, `views`, `uploaded_user_id` FROM `series` VIEW `views_index` WHERE `views` >= $p0", query) - require.Equal(t, []any{driver.NamedValue{Name: "p0", Value: 1000}}, args) - }) - - t.Run("JOIN with VIEW on both tables", func(t *testing.T) { - d := Dialect(dialect.YDB) - series := d.Table("series").View("users_index").As("t1") - users := d.Table("users").View("name_index").As("t2") - - query, args := d.Select(series.C("series_id"), series.C("title")). - From(series). - Join(users). - On(series.C("uploaded_user_id"), users.C("user_id")). - Where(EQ(users.C("name"), "John Doe")). - Query() - - require.Equal(t, "SELECT `t1`.`series_id` AS `series_id`, `t1`.`title` AS `title` FROM `series` VIEW `users_index` AS `t1` JOIN `users` VIEW `name_index` AS `t2` ON `t1`.`uploaded_user_id` = `t2`.`user_id` WHERE `t2`.`name` = $p0", query) - require.Equal(t, []any{driver.NamedValue{Name: "p0", Value: "John Doe"}}, args) - }) - - t.Run("VIEW on non-YDB dialect should error", func(t *testing.T) { - d := Dialect(dialect.Postgres) - table := d.Table("users").View("idx_name") - - err := table.Err() - require.Error(t, err) - }) -} - -func TestBatchUpdate_YDB(t *testing.T) { - t.Run("Basic BATCH UPDATE with SET and WHERE", func(t *testing.T) { - d := Dialect(dialect.YDB) - query, args := d.BatchUpdate("my_table"). - Set("Value1", "foo"). - Set("Value2", 0). - Where(GT("Key1", 1)). - Query() - - require.Equal(t, "BATCH UPDATE `my_table` SET `Value1` = $p0, `Value2` = $p1 WHERE `Key1` > $p2", query) - require.Equal(t, []any{ - driver.NamedValue{Name: "p0", Value: "foo"}, - driver.NamedValue{Name: "p1", Value: 0}, - driver.NamedValue{Name: "p2", Value: 1}, - }, args) - }) - - t.Run("BATCH UPDATE on non-YDB dialect should error", func(t *testing.T) { - builder := Dialect(dialect.MySQL). - BatchUpdate("users"). - Set("status", "active"). - Where(GT("created_at", "2024-01-01")) - - query, args, err := builder.QueryErr() - - require.Empty(t, query) - require.Empty(t, args) - require.Error(t, err) - }) - - t.Run("BATCH UPDATE with RETURNING should error", func(t *testing.T) { - builder := Dialect(dialect.YDB). - BatchUpdate("users"). - Set("status", "active"). - Returning("id", "status") - - query, args, err := builder.QueryErr() - - require.Empty(t, query) - require.Empty(t, args) - require.Error(t, err) - }) - - t.Run("BATCH UPDATE with UPDATE ON pattern should error", func(t *testing.T) { - d := Dialect(dialect.YDB) - subquery := d.Select("id").From(Table("orders")).Where(EQ("status", "pending")) - - builder := d.BatchUpdate("users"). - Set("status", "active"). - On(subquery) - - query, args, err := builder.QueryErr() - - require.Empty(t, query) - require.Empty(t, args) - require.Error(t, err) - require.Contains(t, err.Error(), "BATCH UPDATE: UPDATE ON pattern is not supported") - }) -} - -func TestBatchDelete_YDB(t *testing.T) { - t.Run("Basic BATCH DELETE", func(t *testing.T) { - d := Dialect(dialect.YDB) - query, args := d.BatchDelete("my_table"). - Where(And(GT("Key1", 1), GTE("Key2", "One"))). - Query() - - require.Equal(t, "BATCH DELETE FROM `my_table` WHERE `Key1` > $p0 AND `Key2` >= $p1", query) - require.Equal(t, []any{ - driver.NamedValue{Name: "p0", Value: 1}, - driver.NamedValue{Name: "p1", Value: "One"}, - }, args) - }) - - t.Run("BATCH DELETE on non-YDB dialect should error", func(t *testing.T) { - builder := Dialect(dialect.MySQL). - BatchDelete("users"). - Where(GT("id", 100)) - - query, args, err := builder.QueryErr() - - require.Empty(t, query) - require.Empty(t, args) - require.Error(t, err) - }) - - t.Run("BATCH DELETE with RETURNING should error", func(t *testing.T) { - builder := Dialect(dialect.YDB). - BatchDelete("users"). - Where(GT("id", 100)). - Returning("id") - - query, args, err := builder.QueryErr() - - require.Empty(t, query) - require.Empty(t, args) - require.Error(t, err) - }) - - t.Run("BATCH DELETE with DELETE ON pattern should error", func(t *testing.T) { - d := Dialect(dialect.YDB) - subquery := d.Select("id").From(Table("users")).Where(EQ("status", "deleted")) - - builder := d.BatchDelete("users"). - On(subquery) - - query, args, err := builder.QueryErr() - - require.Empty(t, query) - require.Empty(t, args) - require.Error(t, err) - require.Contains(t, err.Error(), "BATCH DELETE: DELETE ON pattern is not supported") - }) -} - func TestCreateView_YDB(t *testing.T) { t.Run("Basic view with security_invoker", func(t *testing.T) { d := Dialect(dialect.YDB)