diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index d884bf35..f6ec06ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea src *.iml -coverage.* \ No newline at end of file +coverage.* +/.direnv/ diff --git a/exp/exp.go b/exp/exp.go index 240a96a6..4395be03 100644 --- a/exp/exp.go +++ b/exp/exp.go @@ -533,6 +533,7 @@ const ( NaturalRightJoinType NaturalFullJoinType CrossJoinType + CustomJoinType UsingJoinCondType JoinConditionType = iota OnJoinCondType diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..bddf4d3b --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1725634671, + "narHash": "sha256-v3rIhsJBOMLR8e/RNWxr828tB+WywYIoajrZKFM+0Gg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "574d1eac1c200690e27b8eb4e24887f8df7ac27c", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..9f145a49 --- /dev/null +++ b/flake.nix @@ -0,0 +1,28 @@ +{ + description = "goqu"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let pkgs = import nixpkgs { inherit system; }; + + in { + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + go + golangci-lint + gotests + gomodifytags + gore + gotools + + # LSPs + gopls + ]; + }; + }); +} diff --git a/select_dataset.go b/select_dataset.go index 775c387d..2998a346 100644 --- a/select_dataset.go +++ b/select_dataset.go @@ -343,6 +343,11 @@ func (sd *SelectDataset) CrossJoin(table exp.Expression) *SelectDataset { return sd.joinTable(exp.NewUnConditionedJoinExpression(exp.CrossJoinType, table)) } +// Adds a custom join clause. See examples +func (sd *SelectDataset) CustomJoin(expression exp.Expression) *SelectDataset { + return sd.joinTable(exp.NewUnConditionedJoinExpression(exp.CustomJoinType, expression)) +} + // Joins this Datasets table with another func (sd *SelectDataset) joinTable(join exp.JoinExpression) *SelectDataset { return sd.copy(sd.clauses.JoinsAppend(join)) diff --git a/select_dataset_example_test.go b/select_dataset_example_test.go index e8d7fc2d..cb141362 100644 --- a/select_dataset_example_test.go +++ b/select_dataset_example_test.go @@ -15,7 +15,7 @@ import ( const schema = ` DROP TABLE IF EXISTS "user_role"; - DROP TABLE IF EXISTS "goqu_user"; + DROP TABLE IF EXISTS "goqu_user"; CREATE TABLE "goqu_user" ( "id" SERIAL PRIMARY KEY NOT NULL, "first_name" VARCHAR(45) NOT NULL, @@ -27,7 +27,7 @@ const schema = ` "user_id" BIGINT NOT NULL REFERENCES goqu_user(id) ON DELETE CASCADE, "name" VARCHAR(45) NOT NULL, "created" TIMESTAMP NOT NULL DEFAULT now() - ); + ); ` const defaultDBURI = "postgres://postgres:@localhost:5435/goqupostgres?sslmode=disable" @@ -968,6 +968,16 @@ func ExampleSelectDataset_CrossJoin() { // SELECT * FROM "test" CROSS JOIN (SELECT * FROM "test2" WHERE ("amount" > 0)) AS "t" } +func ExampleSelectDataset_CustomJoin() { + join := goqu.L("ARRAY JOIN tags").As("tag") + + sql, _, _ := goqu.From("test").CustomJoin(join).ToSQL() + fmt.Println(sql) + + // Output: + // SELECT * FROM "test" ARRAY JOIN tags AS tag +} + func ExampleSelectDataset_FromSelf() { sql, _, _ := goqu.From("test").FromSelf().ToSQL() fmt.Println(sql) diff --git a/select_dataset_test.go b/select_dataset_test.go index 6579cfad..a85a6172 100644 --- a/select_dataset_test.go +++ b/select_dataset_test.go @@ -625,6 +625,20 @@ func (sds *selectDatasetSuite) TestCrossJoin() { ) } +func (sds *selectDatasetSuite) TestCustomJoin() { + bd := goqu.From("test") + sds.assertCases( + selectTestCase{ + ds: bd.CustomJoin(goqu.L("ARRAY JOIN tags").As("tag")), + clauses: exp.NewSelectClauses(). + SetFrom(exp.NewColumnListExpression("test")). + JoinsAppend( + exp.NewUnConditionedJoinExpression(exp.CustomJoinType, goqu.L("ARRAY JOIN tags").As("tag")), + ), + }, + ) +} + func (sds *selectDatasetSuite) TestWhere() { w := goqu.Ex{"a": 1} w2 := goqu.Ex{"b": "c"} diff --git a/sqlgen/select_sql_generator_test.go b/sqlgen/select_sql_generator_test.go index 61ad589a..e86b0729 100644 --- a/sqlgen/select_sql_generator_test.go +++ b/sqlgen/select_sql_generator_test.go @@ -216,6 +216,7 @@ func (ssgs *selectSQLGeneratorSuite) TestGenerate_withJoin() { opts.JoinTypeLookup = map[exp.JoinType][]byte{ exp.LeftJoinType: []byte(" left join "), exp.NaturalJoinType: []byte(" natural join "), + exp.CustomJoinType: []byte(" "), } sc := exp.NewSelectClauses().SetFrom(exp.NewColumnListExpression("test")) @@ -224,6 +225,7 @@ func (ssgs *selectSQLGeneratorSuite) TestGenerate_withJoin() { cjo := exp.NewConditionedJoinExpression(exp.LeftJoinType, ti, exp.NewJoinOnCondition(exp.Ex{"a": "foo"})) cju := exp.NewConditionedJoinExpression(exp.LeftJoinType, ti, exp.NewJoinUsingCondition("a")) rj := exp.NewConditionedJoinExpression(exp.RightJoinType, ti, exp.NewJoinUsingCondition(exp.NewIdentifierExpression("", "", "a"))) + cj := exp.NewUnConditionedJoinExpression(exp.CustomJoinType, goqu.L("ARRAY JOIN tags").As("tag")) badJoin := exp.NewConditionedJoinExpression(exp.LeftJoinType, ti, exp.NewJoinUsingCondition()) expectedRjError := "goqu: dialect does not support RightJoinType" @@ -254,6 +256,10 @@ func (ssgs *selectSQLGeneratorSuite) TestGenerate_withJoin() { isPrepared: true, args: []interface{}{"foo"}, }, + selectTestCase{ + clause: sc.JoinsAppend(cj), + sql: `SELECT * FROM "test" ARRAY JOIN tags AS "tag"`, + }, selectTestCase{clause: sc.JoinsAppend(rj), err: expectedRjError}, selectTestCase{clause: sc.JoinsAppend(rj), err: expectedRjError, isPrepared: true}, diff --git a/sqlgen/sql_dialect_options.go b/sqlgen/sql_dialect_options.go index a0df394e..1d140d12 100644 --- a/sqlgen/sql_dialect_options.go +++ b/sqlgen/sql_dialect_options.go @@ -547,6 +547,7 @@ func DefaultDialectOptions() *SQLDialectOptions { exp.NaturalRightJoinType: []byte(" NATURAL RIGHT JOIN "), exp.NaturalFullJoinType: []byte(" NATURAL FULL JOIN "), exp.CrossJoinType: []byte(" CROSS JOIN "), + exp.CustomJoinType: []byte(" "), // User need to fill in the join statement themselves }, TimeFormat: time.RFC3339Nano,