From 510cdcff8687c3fa8100caac7b03017ebde0c77d Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Mon, 30 Sep 2024 14:42:26 -0700 Subject: [PATCH 01/12] Added namespaces and expanded set grammar to SET VARIABLE statements --- postgres/parser/parser/sql.y | 10 +++++++++- postgres/parser/sem/tree/set.go | 7 ++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/postgres/parser/parser/sql.y b/postgres/parser/parser/sql.y index 347790450c..86c305e307 100644 --- a/postgres/parser/parser/sql.y +++ b/postgres/parser/parser/sql.y @@ -5973,8 +5973,12 @@ set_session_or_local_cmd: | set_role generic_set_single_config: + name "." name to_or_eq var_list + { + $$.val = &tree.SetVar{Namespace: $1, Name: $3, Values: $5.exprs()} + } // var_value includes DEFAULT expr - name to_or_eq var_list +| name to_or_eq var_list { $$.val = &tree.SetVar{Name: $1, Values: $3.exprs()} } @@ -6224,6 +6228,10 @@ show_session_stmt: session_var: IDENT +| IDENT "." IDENT + { + $$ = $1 + "." + $3 + } // Although ALL, SESSION_USER and DATABASE are identifiers for the // purpose of SHOW, they lex as separate token types, so they need // separate rules. diff --git a/postgres/parser/sem/tree/set.go b/postgres/parser/sem/tree/set.go index 56e0266cd3..c457a1131d 100644 --- a/postgres/parser/sem/tree/set.go +++ b/postgres/parser/sem/tree/set.go @@ -37,9 +37,10 @@ var _ Statement = &SetVar{} // SetVar represents a SET or RESET statement. type SetVar struct { - IsLocal bool - Name string - Values Exprs + IsLocal bool + Name string + Namespace string + Values Exprs // FromCurrent is used for SET clauses in CREATE statements only. FromCurrent bool } From 340e6a76413f5a36efca6fc4f32f9bcf0e3ca05a Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Mon, 30 Sep 2024 14:42:54 -0700 Subject: [PATCH 02/12] Tests and support for custom namespace vars --- server/ast/set_var.go | 32 +++++++++++++------ testing/go/enginetest/doltgres_engine_test.go | 1 - testing/go/set_test.go | 14 ++++++++ 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/server/ast/set_var.go b/server/ast/set_var.go index dfad8ce838..4ca7160246 100644 --- a/server/ast/set_var.go +++ b/server/ast/set_var.go @@ -37,7 +37,7 @@ func nodeSetVar(node *tree.SetVar) (vitess.Statement, error) { dbName = strings.TrimPrefix(strings.TrimSuffix(dbName, "`"), "`") return &vitess.Use{DBName: vitess.NewTableIdent(dbName)}, nil } - if !config.IsValidPostgresConfigParameter(node.Name) && !config.IsValidDoltConfigParameter(node.Name) { + if node.Namespace == "" && !config.IsValidPostgresConfigParameter(node.Name) && !config.IsValidDoltConfigParameter(node.Name) { return nil, fmt.Errorf(`ERROR: unrecognized configuration parameter "%s"`, node.Name) } if node.IsLocal { @@ -63,14 +63,26 @@ func nodeSetVar(node *tree.SetVar) (vitess.Statement, error) { return nil, err } } - setStmt := &vitess.Set{ - Exprs: vitess.SetVarExprs{&vitess.SetVarExpr{ - Scope: vitess.SetScope_Session, - Name: &vitess.ColName{ - Name: vitess.NewColIdent(node.Name), - }, - Expr: expr, - }}, + + if node.Namespace == "" { + return &vitess.Set{ + Exprs: vitess.SetVarExprs{&vitess.SetVarExpr{ + Scope: vitess.SetScope_Session, + Name: &vitess.ColName{ + Name: vitess.NewColIdent(node.Name), + }, + Expr: expr, + }}, + }, nil + } else { + return &vitess.Set{ + Exprs: vitess.SetVarExprs{&vitess.SetVarExpr{ + Scope: vitess.SetScope_User, + Name: &vitess.ColName{ + Name: vitess.NewColIdent(fmt.Sprintf("%s.%s", node.Namespace, node.Name)), + }, + Expr: expr, + }}, + }, nil } - return setStmt, nil } diff --git a/testing/go/enginetest/doltgres_engine_test.go b/testing/go/enginetest/doltgres_engine_test.go index d1a8723b77..762b76de78 100755 --- a/testing/go/enginetest/doltgres_engine_test.go +++ b/testing/go/enginetest/doltgres_engine_test.go @@ -1247,7 +1247,6 @@ func TestBrokenSystemTableQueries(t *testing.T) { } func TestHistorySystemTable(t *testing.T) { - t.Skip() harness := newDoltgresServerHarness(t).WithParallelism(2) denginetest.RunHistorySystemTableTests(t, harness) } diff --git a/testing/go/set_test.go b/testing/go/set_test.go index afac89fd0b..dd5a149190 100644 --- a/testing/go/set_test.go +++ b/testing/go/set_test.go @@ -7371,4 +7371,18 @@ var setStmts = []ScriptTest{ }, }, }, + { + Name: "custom settings", + Focus: true, + Assertions: []ScriptTestAssertion{ + { + Query: "SET custom.setting TO 'value'", + Expected: []sql.Row{}, + }, + { + Query: "SHOW custom.setting", + Expected: []sql.Row{{"value"}}, + }, + }, + }, } From 774bc6e9c5fc876db8cca543005fa0eecbebe937 Mon Sep 17 00:00:00 2001 From: jennifersp Date: Tue, 1 Oct 2024 15:33:12 -0700 Subject: [PATCH 03/12] add translate() and quote_ident() and pg_operator --- server/functions/init.go | 2 + server/functions/quote_ident.go | 41 ++++++++++++ server/functions/translate.go | 56 ++++++++++++++++ server/tables/pgcatalog/init.go | 1 + server/tables/pgcatalog/pg_opclass.go | 1 + server/tables/pgcatalog/pg_operator.go | 91 ++++++++++++++++++++++++++ testing/go/functions_test.go | 44 +++++++++++++ 7 files changed, 236 insertions(+) create mode 100644 server/functions/quote_ident.go create mode 100644 server/functions/translate.go create mode 100644 server/tables/pgcatalog/pg_operator.go diff --git a/server/functions/init.go b/server/functions/init.go index bf635fcb01..758f6ee6a8 100644 --- a/server/functions/init.go +++ b/server/functions/init.go @@ -94,6 +94,7 @@ func Init() { initPgTotalRelationSize() initPi() initPower() + initQuoteIdent() initRadians() initRandom() initRepeat() @@ -122,6 +123,7 @@ func Init() { initToRegclass() initToRegproc() initToRegtype() + initTranslate() initTrimScale() initTrunc() initTxidCurrent() diff --git a/server/functions/quote_ident.go b/server/functions/quote_ident.go new file mode 100644 index 0000000000..c49a740981 --- /dev/null +++ b/server/functions/quote_ident.go @@ -0,0 +1,41 @@ +// Copyright 2024 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package functions + +import ( + "fmt" + "strings" + + "github.com/dolthub/go-mysql-server/sql" + + "github.com/dolthub/doltgresql/server/functions/framework" + pgtypes "github.com/dolthub/doltgresql/server/types" +) + +// initQuoteIdent registers the functions to the catalog. +func initQuoteIdent() { + framework.RegisterFunction(quote_ident_text) +} + +// quote_ident_text represents the PostgreSQL function of the same name, taking the same parameters. +var quote_ident_text = framework.Function1{ + Name: "quote_ident", + Return: pgtypes.Text, + Parameters: [1]pgtypes.DoltgresType{pgtypes.Text}, + Strict: true, + Callable: func(ctx *sql.Context, _ [2]pgtypes.DoltgresType, val any) (any, error) { + return fmt.Sprintf(`"%s"`, strings.Replace(val.(string), "\"", "\"\"", -1)), nil + }, +} diff --git a/server/functions/translate.go b/server/functions/translate.go new file mode 100644 index 0000000000..9ab1b157d5 --- /dev/null +++ b/server/functions/translate.go @@ -0,0 +1,56 @@ +// Copyright 2024 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package functions + +import ( + "github.com/dolthub/go-mysql-server/sql" + + "github.com/dolthub/doltgresql/server/functions/framework" + pgtypes "github.com/dolthub/doltgresql/server/types" +) + +// initTranslate registers the functions to the catalog. +func initTranslate() { + framework.RegisterFunction(translate_text_text_text) +} + +// translate_text_text_text represents the PostgreSQL function of the same name, taking the same parameters. +var translate_text_text_text = framework.Function3{ + Name: "translate", + Return: pgtypes.Text, + Parameters: [3]pgtypes.DoltgresType{pgtypes.Text, pgtypes.Text, pgtypes.Text}, + Strict: true, + Callable: func(ctx *sql.Context, _ [4]pgtypes.DoltgresType, val1, val2, val3 any) (any, error) { + str := val1.(string) + from := []rune(val2.(string)) + to := []rune(val3.(string)) + toLen := len(to) + fromMap := make(map[string]int) + for i, l := range from { + fromMap[string(l)] = i + } + var newStr []rune + for _, l := range str { + if idx, exists := fromMap[string(l)]; exists { + if idx < toLen { + newStr = append(newStr, to[idx]) + } + } else { + newStr = append(newStr, l) + } + } + return string(newStr), nil + }, +} diff --git a/server/tables/pgcatalog/init.go b/server/tables/pgcatalog/init.go index e5e04495fa..7fa5e8eedb 100644 --- a/server/tables/pgcatalog/init.go +++ b/server/tables/pgcatalog/init.go @@ -63,6 +63,7 @@ func Init() { InitPgMatviews() InitPgNamespace() InitPgOpclass() + InitPgOperator() InitPgOpfamily() InitPgParameterAcl() InitPgPartitionedTable() diff --git a/server/tables/pgcatalog/pg_opclass.go b/server/tables/pgcatalog/pg_opclass.go index 108fcc20fa..c80eef2a27 100644 --- a/server/tables/pgcatalog/pg_opclass.go +++ b/server/tables/pgcatalog/pg_opclass.go @@ -62,6 +62,7 @@ var pgOpclassSchema = sql.Schema{ {Name: "opcname", Type: pgtypes.Name, Default: nil, Nullable: false, Source: PgOpclassName}, {Name: "opcnamespace", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOpclassName}, {Name: "opcowner", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOpclassName}, + {Name: "opcfamily", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOpclassName}, {Name: "opcintype", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOpclassName}, {Name: "opcdefault", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgOpclassName}, {Name: "opckeytype", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOpclassName}, diff --git a/server/tables/pgcatalog/pg_operator.go b/server/tables/pgcatalog/pg_operator.go new file mode 100644 index 0000000000..066e4a90e2 --- /dev/null +++ b/server/tables/pgcatalog/pg_operator.go @@ -0,0 +1,91 @@ +// Copyright 2024 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pgcatalog + +import ( + "io" + + "github.com/dolthub/go-mysql-server/sql" + + "github.com/dolthub/doltgresql/server/tables" + pgtypes "github.com/dolthub/doltgresql/server/types" +) + +// PgOperatorName is a constant to the pg_operator name. +const PgOperatorName = "pg_operator" + +// InitPgOperator handles registration of the pg_operator handler. +func InitPgOperator() { + tables.AddHandler(PgCatalogName, PgOperatorName, PgOperatorHandler{}) +} + +// PgOperatorHandler is the handler for the pg_operator table. +type PgOperatorHandler struct{} + +var _ tables.Handler = PgOperatorHandler{} + +// Name implements the interface tables.Handler. +func (p PgOperatorHandler) Name() string { + return PgOperatorName +} + +// RowIter implements the interface tables.Handler. +func (p PgOperatorHandler) RowIter(ctx *sql.Context) (sql.RowIter, error) { + // TODO: Implement pg_operator row iter + return emptyRowIter() +} + +// Schema implements the interface tables.Handler. +func (p PgOperatorHandler) Schema() sql.PrimaryKeySchema { + return sql.PrimaryKeySchema{ + Schema: pgOperatorSchema, + PkOrdinals: nil, + } +} + +// pgOperatorSchema is the schema for pg_operator. +var pgOperatorSchema = sql.Schema{ + {Name: "oid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprname", Type: pgtypes.Name, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprnamespace", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprowner", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprkind", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprcanmerge", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprcanhash", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprleft", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprright", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprresult", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprcom", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprnegate", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprcode", Type: pgtypes.Regproc, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprrest", Type: pgtypes.Regproc, Default: nil, Nullable: false, Source: PgOperatorName}, + {Name: "oprjoin", Type: pgtypes.Regproc, Default: nil, Nullable: false, Source: PgOperatorName}, +} + +// pgOperatorRowIter is the sql.RowIter for the pg_operator table. +type pgOperatorRowIter struct { +} + +var _ sql.RowIter = (*pgOperatorRowIter)(nil) + +// Next implements the interface sql.RowIter. +func (iter *pgOperatorRowIter) Next(ctx *sql.Context) (sql.Row, error) { + return nil, io.EOF +} + +// Close implements the interface sql.RowIter. +func (iter *pgOperatorRowIter) Close(ctx *sql.Context) error { + return nil +} diff --git a/testing/go/functions_test.go b/testing/go/functions_test.go index e344d84203..8228fd7c03 100644 --- a/testing/go/functions_test.go +++ b/testing/go/functions_test.go @@ -1829,5 +1829,49 @@ func TestStringFunction(t *testing.T) { }, }, }, + { + Name: "quote_ident", + SetUpScript: []string{}, + Assertions: []ScriptTestAssertion{ + { + Query: `select quote_ident('hi"bye');`, + Expected: []sql.Row{{`"hi""bye"`}}, + }, + { + Query: `select quote_ident('hi""bye');`, + Expected: []sql.Row{{`"hi""""bye"`}}, + }, + { + Query: `select quote_ident('hi"""bye');`, + Expected: []sql.Row{{`"hi""""""bye"`}}, + }, + { + Query: `select quote_ident('hi"b"ye');`, + Expected: []sql.Row{{`"hi""b""ye"`}}, + }, + }, + }, + { + Name: "translate", + SetUpScript: []string{}, + Assertions: []ScriptTestAssertion{ + { + Query: `select translate('12345', '143', 'ax');`, + Expected: []sql.Row{{`a2x5`}}, + }, + { + Query: `select translate('12345', '143', 'axs');`, + Expected: []sql.Row{{`a2sx5`}}, + }, + { + Query: `select translate('12345', '143', 'axsl');`, + Expected: []sql.Row{{`a2sx5`}}, + }, + { + Query: `select translate('こんにちは', 'ん', 'a');`, + Expected: []sql.Row{{`こaにちは`}}, + }, + }, + }, }) } From 8be154dadaabf22add69ed693c87efb520f1a3ea Mon Sep 17 00:00:00 2001 From: jennifersp Date: Tue, 1 Oct 2024 16:41:27 -0700 Subject: [PATCH 04/12] fix date format parsing --- postgres/parser/pgdate/math.go | 13 +++- server/tables/pgcatalog/init.go | 1 - server/tables/pgcatalog/pg_stat_io.go | 94 --------------------------- server/types/date.go | 13 ++-- testing/go/types_test.go | 36 ++++++++++ 5 files changed, 56 insertions(+), 101 deletions(-) delete mode 100644 server/tables/pgcatalog/pg_stat_io.go diff --git a/postgres/parser/pgdate/math.go b/postgres/parser/pgdate/math.go index fe523b4bb0..be4176b4be 100644 --- a/postgres/parser/pgdate/math.go +++ b/postgres/parser/pgdate/math.go @@ -144,14 +144,25 @@ func chunk(s string, buf []stringChunk) (int, string) { return true } + lastSeenDigit := false + lastSeenLetter := false for offset, r := range s { - if unicode.IsDigit(r) || unicode.IsLetter(r) { + isDigit := unicode.IsDigit(r) + isLetter := unicode.IsLetter(r) + if isDigit || isLetter { + if (isDigit && lastSeenLetter) || (isLetter && lastSeenDigit) { + if !flush() { + return -1, "" + } + } if matchStart >= matchEnd { matchStart = offset } // We're guarded by IsDigit() || IsLetter() above, so // RuneLen() should always return a reasonable value. matchEnd = offset + utf8.RuneLen(r) + lastSeenDigit = isDigit + lastSeenLetter = isLetter } else if !flush() { return -1, "" } diff --git a/server/tables/pgcatalog/init.go b/server/tables/pgcatalog/init.go index 7fa5e8eedb..cc65f00b31 100644 --- a/server/tables/pgcatalog/init.go +++ b/server/tables/pgcatalog/init.go @@ -101,7 +101,6 @@ func Init() { InitPgStatDatabase() InitPgStatDatabaseConflicts() InitPgStatGssapi() - InitPgStatIo() InitPgStatProgressAnalyze() InitPgStatProgressBasebackup() InitPgStatProgressCluster() diff --git a/server/tables/pgcatalog/pg_stat_io.go b/server/tables/pgcatalog/pg_stat_io.go deleted file mode 100644 index 0c6848580b..0000000000 --- a/server/tables/pgcatalog/pg_stat_io.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2024 Dolthub, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pgcatalog - -import ( - "io" - - "github.com/dolthub/go-mysql-server/sql" - - "github.com/dolthub/doltgresql/server/tables" - pgtypes "github.com/dolthub/doltgresql/server/types" -) - -// PgStatIoName is a constant to the pg_stat_io name. -const PgStatIoName = "pg_stat_io" - -// InitPgStatIo handles registration of the pg_stat_io handler. -func InitPgStatIo() { - tables.AddHandler(PgCatalogName, PgStatIoName, PgStatIoHandler{}) -} - -// PgStatIoHandler is the handler for the pg_stat_io table. -type PgStatIoHandler struct{} - -var _ tables.Handler = PgStatIoHandler{} - -// Name implements the interface tables.Handler. -func (p PgStatIoHandler) Name() string { - return PgStatIoName -} - -// RowIter implements the interface tables.Handler. -func (p PgStatIoHandler) RowIter(ctx *sql.Context) (sql.RowIter, error) { - // TODO: Implement pg_stat_io row iter - return emptyRowIter() -} - -// Schema implements the interface tables.Handler. -func (p PgStatIoHandler) Schema() sql.PrimaryKeySchema { - return sql.PrimaryKeySchema{ - Schema: pgStatIoSchema, - PkOrdinals: nil, - } -} - -// pgStatIoSchema is the schema for pg_stat_io. -var pgStatIoSchema = sql.Schema{ - {Name: "backend_type", Type: pgtypes.Text, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "object", Type: pgtypes.Text, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "context", Type: pgtypes.Text, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "reads", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "read_time", Type: pgtypes.Float64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "writes", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "write_time", Type: pgtypes.Float64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "writebacks", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "writeback_time", Type: pgtypes.Float64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "extends", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "extend_time", Type: pgtypes.Float64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "op_bytes", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "hits", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "evictions", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "reuses", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "fsyncs", Type: pgtypes.Int64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "fsync_time", Type: pgtypes.Float64, Default: nil, Nullable: true, Source: PgStatIoName}, - {Name: "stats_reset", Type: pgtypes.TimestampTZ, Default: nil, Nullable: true, Source: PgStatIoName}, -} - -// pgStatIoRowIter is the sql.RowIter for the pg_stat_io table. -type pgStatIoRowIter struct { -} - -var _ sql.RowIter = (*pgStatIoRowIter)(nil) - -// Next implements the interface sql.RowIter. -func (iter *pgStatIoRowIter) Next(ctx *sql.Context) (sql.Row, error) { - return nil, io.EOF -} - -// Close implements the interface sql.RowIter. -func (iter *pgStatIoRowIter) Close(ctx *sql.Context) error { - return nil -} diff --git a/server/types/date.go b/server/types/date.go index 43278f8997..538c2ba999 100644 --- a/server/types/date.go +++ b/server/types/date.go @@ -17,11 +17,10 @@ package types import ( "bytes" "fmt" + "github.com/dolthub/doltgresql/postgres/parser/pgdate" "reflect" "time" - "github.com/dolthub/doltgresql/postgres/parser/sem/tree" - "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" "github.com/dolthub/vitess/go/sqltypes" @@ -121,11 +120,15 @@ func (b DateType) GetSerializationID() SerializationID { // IoInput implements the DoltgresType interface. func (b DateType) IoInput(ctx *sql.Context, input string) (any, error) { - ddate, _, err := tree.ParseDDate(nil, input) - if err != nil { + if date, _, err := pgdate.ParseDate(time.Now(), pgdate.ParseModeYMD, input); err == nil { + return date.ToTime() + } else if date, _, err = pgdate.ParseDate(time.Now(), pgdate.ParseModeDMY, input); err == nil { + return date.ToTime() + } else if date, _, err = pgdate.ParseDate(time.Now(), pgdate.ParseModeMDY, input); err == nil { + return date.ToTime() + } else { return nil, err } - return ddate.Date.ToTime() } // IoOutput implements the DoltgresType interface. diff --git a/testing/go/types_test.go b/testing/go/types_test.go index 0fee5eb93e..b0c225d969 100644 --- a/testing/go/types_test.go +++ b/testing/go/types_test.go @@ -561,6 +561,42 @@ var typesTests = []ScriptTest{ {"2022-02-02"}, }, }, + { + Query: "select '2024-10-31'::date;", + Expected: []sql.Row{ + {"2024-10-31"}, + }, + }, + { + Query: "select '2024-OCT-31'::date;", + Expected: []sql.Row{ + {"2024-10-31"}, + }, + }, + { + Query: "select '20241031'::date;", + Expected: []sql.Row{ + {"2024-10-31"}, + }, + }, + { + Query: "select '2024Oct31'::date;", + Expected: []sql.Row{ + {"2024-10-31"}, + }, + }, + { + Query: "select '10 31 2024'::date;", + Expected: []sql.Row{ + {"2024-10-31"}, + }, + }, + { + Query: "select 'Oct 31 2024'::date;", + Expected: []sql.Row{ + {"2024-10-31"}, + }, + }, }, }, { From e27380110bce9636978d9ed6e4db089b7e4d8e4a Mon Sep 17 00:00:00 2001 From: jennifersp Date: Tue, 1 Oct 2024 16:45:15 -0700 Subject: [PATCH 05/12] add test for pg_operator --- testing/go/pgcatalog_test.go | 52 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/testing/go/pgcatalog_test.go b/testing/go/pgcatalog_test.go index e78ee42474..7462205528 100644 --- a/testing/go/pgcatalog_test.go +++ b/testing/go/pgcatalog_test.go @@ -1468,6 +1468,32 @@ func TestPgOpclass(t *testing.T) { }) } +func TestPgOperator(t *testing.T) { + RunScripts(t, []ScriptTest{ + { + Name: "pg_operator", + Assertions: []ScriptTestAssertion{ + { + Query: `SELECT * FROM "pg_catalog"."pg_operator";`, + Expected: []sql.Row{}, + }, + { // Different cases and quoted, so it fails + Query: `SELECT * FROM "PG_catalog"."pg_operator";`, + ExpectedErr: "not", + }, + { // Different cases and quoted, so it fails + Query: `SELECT * FROM "pg_catalog"."PG_operator";`, + ExpectedErr: "not", + }, + { // Different cases but non-quoted, so it works + Query: "SELECT oprname FROM PG_catalog.pg_OPERATOR ORDER BY oprname;", + Expected: []sql.Row{}, + }, + }, + }, + }) +} + func TestPgOpfamily(t *testing.T) { RunScripts(t, []ScriptTest{ { @@ -2404,32 +2430,6 @@ func TestPgStatGssapi(t *testing.T) { }) } -func TestPgStatIo(t *testing.T) { - RunScripts(t, []ScriptTest{ - { - Name: "pg_stat_io", - Assertions: []ScriptTestAssertion{ - { - Query: `SELECT * FROM "pg_catalog"."pg_stat_io";`, - Expected: []sql.Row{}, - }, - { // Different cases and quoted, so it fails - Query: `SELECT * FROM "PG_catalog"."pg_stat_io";`, - ExpectedErr: "not", - }, - { // Different cases and quoted, so it fails - Query: `SELECT * FROM "pg_catalog"."PG_stat_io";`, - ExpectedErr: "not", - }, - { // Different cases but non-quoted, so it works - Query: "SELECT object FROM PG_catalog.pg_STAT_IO ORDER BY object;", - Expected: []sql.Row{}, - }, - }, - }, - }) -} - func TestPgStatProgressAnalyze(t *testing.T) { RunScripts(t, []ScriptTest{ { From b89a9dea3922cb314eb889fbf4d3e3a4625b42ed Mon Sep 17 00:00:00 2001 From: jennifersp Date: Tue, 1 Oct 2024 16:56:29 -0700 Subject: [PATCH 06/12] format --- server/types/date.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/types/date.go b/server/types/date.go index 538c2ba999..2d26efb294 100644 --- a/server/types/date.go +++ b/server/types/date.go @@ -17,10 +17,11 @@ package types import ( "bytes" "fmt" - "github.com/dolthub/doltgresql/postgres/parser/pgdate" "reflect" "time" + "github.com/dolthub/doltgresql/postgres/parser/pgdate" + "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" "github.com/dolthub/vitess/go/sqltypes" From 71a2c84b6ab9d48a0255fe9794b430f574d90bb6 Mon Sep 17 00:00:00 2001 From: jennifersp Date: Tue, 1 Oct 2024 17:05:31 -0700 Subject: [PATCH 07/12] fix julian parsing --- postgres/parser/pgdate/math.go | 5 ++++- testing/go/types_test.go | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/postgres/parser/pgdate/math.go b/postgres/parser/pgdate/math.go index be4176b4be..a83bafadfb 100644 --- a/postgres/parser/pgdate/math.go +++ b/postgres/parser/pgdate/math.go @@ -25,6 +25,7 @@ package pgdate import ( + "strings" "unicode" "unicode/utf8" ) @@ -144,13 +145,15 @@ func chunk(s string, buf []stringChunk) (int, string) { return true } + maybeJulian := strings.HasPrefix(strings.ToLower(s), "j") + lastSeenDigit := false lastSeenLetter := false for offset, r := range s { isDigit := unicode.IsDigit(r) isLetter := unicode.IsLetter(r) if isDigit || isLetter { - if (isDigit && lastSeenLetter) || (isLetter && lastSeenDigit) { + if (isDigit && lastSeenLetter && !maybeJulian) || (isLetter && lastSeenDigit) { if !flush() { return -1, "" } diff --git a/testing/go/types_test.go b/testing/go/types_test.go index b0c225d969..5a7115f83d 100644 --- a/testing/go/types_test.go +++ b/testing/go/types_test.go @@ -597,6 +597,12 @@ var typesTests = []ScriptTest{ {"2024-10-31"}, }, }, + { + Query: "SELECT date 'J2451187';", + Expected: []sql.Row{ + {"1999-01-08"}, + }, + }, }, }, { From 34ed0da45c0b2cda4af60273e8050feac980f3fa Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 1 Oct 2024 17:15:16 -0700 Subject: [PATCH 08/12] Support system settings with namespaces (used to emulate user vars) --- postgres/parser/parser/sql.y | 4 ++-- server/ast/show_var.go | 10 ++++++++-- server/doltgres_handler.go | 16 +++++++++++++++- testing/go/set_test.go | 6 +++--- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/postgres/parser/parser/sql.y b/postgres/parser/parser/sql.y index 9626820449..02b69ca6dc 100644 --- a/postgres/parser/parser/sql.y +++ b/postgres/parser/parser/sql.y @@ -6032,7 +6032,7 @@ set_session_or_local_cmd: | set_role generic_set_single_config: - name "." name to_or_eq var_list + name '.' name to_or_eq var_list { $$.val = &tree.SetVar{Namespace: $1, Name: $3, Values: $5.exprs()} } @@ -6287,7 +6287,7 @@ show_session_stmt: session_var: IDENT -| IDENT "." IDENT +| IDENT '.' IDENT { $$ = $1 + "." + $3 } diff --git a/server/ast/show_var.go b/server/ast/show_var.go index ea688026a5..adb7173b83 100644 --- a/server/ast/show_var.go +++ b/server/ast/show_var.go @@ -38,16 +38,22 @@ func nodeShowVar(node *tree.ShowVar) (vitess.Statement, error) { // TODO: this is a temporary way to get the param value for the current implementation // need better way to get these info + varName := vitess.NewColIdent("@@session." + node.Name) + // We treat namespaced variables (e.g. myvar.myvalue) as user variables. + // See set_var.go + if strings.Index(node.Name, ".") > 0 { + varName = vitess.NewColIdent("@" + node.Name) + } s := &vitess.Select{ SelectExprs: vitess.SelectExprs{ &vitess.AliasedExpr{ Expr: &vitess.ColName{ - Name: vitess.NewColIdent("@@session." + node.Name), + Name: varName, Qualifier: vitess.TableName{}, }, StartParsePos: 7, EndParsePos: 17 + len(node.Name), - As: vitess.NewColIdent(node.Name), + As: varName, }, }, } diff --git a/server/doltgres_handler.go b/server/doltgres_handler.go index 97b2316dda..43928339b0 100644 --- a/server/doltgres_handler.go +++ b/server/doltgres_handler.go @@ -19,6 +19,7 @@ import ( "encoding/base64" "fmt" "io" + "os" "regexp" "runtime/trace" "sync" @@ -65,6 +66,16 @@ type DoltgresHandler struct { var _ Handler = &DoltgresHandler{} +var printErrorStackTraces = true + +const PrintErrorStackTracesEnvKey = "DOLTGRES_PRINT_ERROR_STACK_TRACES" + +func init() { + if _, ok := os.LookupEnv(PrintErrorStackTracesEnvKey); ok { + printErrorStackTraces = true + } +} + // ComBind implements the Handler interface. func (h *DoltgresHandler) ComBind(ctx context.Context, c *mysql.Conn, query string, parsedQuery mysql.ParsedQuery, bindVars map[string]sqlparser.Expr) (mysql.BoundQuery, []pgproto3.FieldDescription, error) { sqlCtx, err := h.sm.NewContextWithQuery(ctx, c, query) @@ -109,6 +120,9 @@ func (h *DoltgresHandler) ComPrepareParsed(ctx context.Context, c *mysql.Conn, q analyzed, err := h.e.PrepareParsedQuery(sqlCtx, query, query, parsed) if err != nil { + if printErrorStackTraces { + fmt.Printf("unable to prepare query: %+v\n", err) + } logrus.WithField("query", query).Errorf("unable to prepare query: %s", err.Error()) err := sql.CastSQLError(err) return nil, nil, err @@ -451,7 +465,7 @@ func (h *DoltgresHandler) resultForDefaultIter(ctx *sql.Context, schema sql.Sche // and calls |callback| to give them to vitess. eg.Go(func() error { defer pan2err() - //defer cancelF() + // defer cancelF() defer wg.Done() for { if r == nil { diff --git a/testing/go/set_test.go b/testing/go/set_test.go index dd5a149190..ee825623fa 100644 --- a/testing/go/set_test.go +++ b/testing/go/set_test.go @@ -7372,15 +7372,15 @@ var setStmts = []ScriptTest{ }, }, { - Name: "custom settings", + Name: "settings with namespaces", Focus: true, Assertions: []ScriptTestAssertion{ { - Query: "SET custom.setting TO 'value'", + Query: "SET myvar.var_value TO 'value'", Expected: []sql.Row{}, }, { - Query: "SHOW custom.setting", + Query: "SHOW myvar.var_value", Expected: []sql.Row{{"value"}}, }, }, From 7c1c5a815ef4de48c3b7eeb2cd00a41da295ac4a Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 1 Oct 2024 17:17:12 -0700 Subject: [PATCH 09/12] More stack traces --- server/doltgres_handler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/doltgres_handler.go b/server/doltgres_handler.go index 43928339b0..3ce2d785be 100644 --- a/server/doltgres_handler.go +++ b/server/doltgres_handler.go @@ -232,6 +232,9 @@ func (h *DoltgresHandler) doQuery(ctx context.Context, c *mysql.Conn, query stri schema, rowIter, qFlags, err := queryExec(sqlCtx, query, parsed, analyzedPlan) if err != nil { + if printErrorStackTraces { + fmt.Printf("error running query: %+v\n", err) + } sqlCtx.GetLogger().WithError(err).Warn("error running query") return err } From 8082883287a93152ff65e9eb88f675701a5121c6 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 1 Oct 2024 17:52:57 -0700 Subject: [PATCH 10/12] Implemented current_setting() and more coverage for same --- server/ast/show_var.go | 48 +++++++++++++++-------- server/doltgres_handler.go | 20 +++++----- server/functions/current_setting.go | 59 +++++++++++++++++++++++++++++ server/functions/init.go | 1 + testing/go/set_test.go | 12 ++++++ 5 files changed, 115 insertions(+), 25 deletions(-) create mode 100755 server/functions/current_setting.go diff --git a/server/ast/show_var.go b/server/ast/show_var.go index adb7173b83..c7f8cfef24 100644 --- a/server/ast/show_var.go +++ b/server/ast/show_var.go @@ -38,24 +38,42 @@ func nodeShowVar(node *tree.ShowVar) (vitess.Statement, error) { // TODO: this is a temporary way to get the param value for the current implementation // need better way to get these info - varName := vitess.NewColIdent("@@session." + node.Name) // We treat namespaced variables (e.g. myvar.myvalue) as user variables. // See set_var.go - if strings.Index(node.Name, ".") > 0 { - varName = vitess.NewColIdent("@" + node.Name) - } - s := &vitess.Select{ - SelectExprs: vitess.SelectExprs{ - &vitess.AliasedExpr{ - Expr: &vitess.ColName{ - Name: varName, - Qualifier: vitess.TableName{}, + isUserVar := strings.Index(node.Name, ".") > 0 + if isUserVar { + varName := vitess.NewColIdent(node.Name) + return &vitess.Select{ + SelectExprs: vitess.SelectExprs{ + &vitess.AliasedExpr{ + Expr: &vitess.FuncExpr{ + Name: vitess.NewColIdent("current_setting"), + Exprs: []vitess.SelectExpr{ + &vitess.AliasedExpr{ + Expr: &vitess.SQLVal{Type: vitess.StrVal, Val: []byte(node.Name)}, + }, + }, + }, + StartParsePos: 7, + EndParsePos: 7 + len(varName.String()), + As: varName, + }, + }, + }, nil + } else { + varName := vitess.NewColIdent("@@session." + node.Name) + return &vitess.Select{ + SelectExprs: vitess.SelectExprs{ + &vitess.AliasedExpr{ + Expr: &vitess.ColName{ + Name: varName, + Qualifier: vitess.TableName{}, + }, + StartParsePos: 7, + EndParsePos: 7 + len(varName.String()), + As: varName, }, - StartParsePos: 7, - EndParsePos: 17 + len(node.Name), - As: varName, }, - }, + }, nil } - return s, nil } diff --git a/server/doltgres_handler.go b/server/doltgres_handler.go index 3ce2d785be..ed0ef103e0 100644 --- a/server/doltgres_handler.go +++ b/server/doltgres_handler.go @@ -39,6 +39,16 @@ import ( pgtypes "github.com/dolthub/doltgresql/server/types" ) +var printErrorStackTraces = false + +const PrintErrorStackTracesEnvKey = "DOLTGRES_PRINT_ERROR_STACK_TRACES" + +func init() { + if _, ok := os.LookupEnv(PrintErrorStackTracesEnvKey); ok { + printErrorStackTraces = true + } +} + // Result represents a query result. type Result struct { Fields []pgproto3.FieldDescription `json:"fields"` @@ -66,16 +76,6 @@ type DoltgresHandler struct { var _ Handler = &DoltgresHandler{} -var printErrorStackTraces = true - -const PrintErrorStackTracesEnvKey = "DOLTGRES_PRINT_ERROR_STACK_TRACES" - -func init() { - if _, ok := os.LookupEnv(PrintErrorStackTracesEnvKey); ok { - printErrorStackTraces = true - } -} - // ComBind implements the Handler interface. func (h *DoltgresHandler) ComBind(ctx context.Context, c *mysql.Conn, query string, parsedQuery mysql.ParsedQuery, bindVars map[string]sqlparser.Expr) (mysql.BoundQuery, []pgproto3.FieldDescription, error) { sqlCtx, err := h.sm.NewContextWithQuery(ctx, c, query) diff --git a/server/functions/current_setting.go b/server/functions/current_setting.go new file mode 100755 index 0000000000..1eaa68dca4 --- /dev/null +++ b/server/functions/current_setting.go @@ -0,0 +1,59 @@ +// Copyright 2024 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package functions + +import ( + "fmt" + + "github.com/dolthub/go-mysql-server/sql" + + "github.com/dolthub/doltgresql/server/functions/framework" + pgtypes "github.com/dolthub/doltgresql/server/types" +) + +// initAsin registers the functions to the catalog. +func initCurrentSetting() { + framework.RegisterFunction(current_setting) +} + +// asin_float64 represents the PostgreSQL function of the same name, taking the same parameters. +var current_setting = framework.Function1{ + Name: "current_setting", + Return: pgtypes.Text, // TODO: it would be nice to support non-text values as well, but this is all postgres supports + Parameters: [1]pgtypes.DoltgresType{pgtypes.Text}, + Strict: true, + Callable: func(ctx *sql.Context, _ [2]pgtypes.DoltgresType, val1 any) (any, error) { + s := val1.(string) + _, variable, err := ctx.GetUserVariable(ctx, s) + if err != nil { + return nil, err + } + + if variable != nil { + return variable, nil + } + + variable, err = ctx.GetSessionVariable(ctx, s) + if err != nil { + return nil, fmt.Errorf("unrecognized configuration parameter %s", s) + } + + if variable != nil { + return variable, nil + } + + return nil, fmt.Errorf("unrecognized configuration parameter %s", s) + }, +} diff --git a/server/functions/init.go b/server/functions/init.go index bf635fcb01..36ad80c7d8 100644 --- a/server/functions/init.go +++ b/server/functions/init.go @@ -46,6 +46,7 @@ func Init() { initCotd() initCurrentDatabase() initCurrentSchema() + initCurrentSetting() initCurrentSchemas() initDegrees() initDiv() diff --git a/testing/go/set_test.go b/testing/go/set_test.go index ee825623fa..34b50bf7c5 100644 --- a/testing/go/set_test.go +++ b/testing/go/set_test.go @@ -7383,6 +7383,18 @@ var setStmts = []ScriptTest{ Query: "SHOW myvar.var_value", Expected: []sql.Row{{"value"}}, }, + { + Query: "select current_setting('myvar.var_value')", + Expected: []sql.Row{{"value"}}, + }, + { + Query: "select current_setting('unknown_var')", + ExpectedErr: "unrecognized configuration parameter", + }, + { + Query: "show myvar.unknown_var", + ExpectedErr: "unrecognized configuration parameter", + }, }, }, } From b2346febb15aa0cc01f3d0cef8ef2a219a45f697 Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 1 Oct 2024 18:01:53 -0700 Subject: [PATCH 11/12] unfocus test --- testing/go/set_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testing/go/set_test.go b/testing/go/set_test.go index 34b50bf7c5..85f8aebfba 100644 --- a/testing/go/set_test.go +++ b/testing/go/set_test.go @@ -7372,8 +7372,7 @@ var setStmts = []ScriptTest{ }, }, { - Name: "settings with namespaces", - Focus: true, + Name: "settings with namespaces", Assertions: []ScriptTestAssertion{ { Query: "SET myvar.var_value TO 'value'", From 0bd9a177eff2bc91e4ec4974903e2c2d8f06cbcf Mon Sep 17 00:00:00 2001 From: Zach Musgrave Date: Tue, 1 Oct 2024 18:14:31 -0700 Subject: [PATCH 12/12] Re-skip test not ready yet --- testing/go/enginetest/doltgres_engine_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/go/enginetest/doltgres_engine_test.go b/testing/go/enginetest/doltgres_engine_test.go index 0d1866f17e..1dbbd795ab 100755 --- a/testing/go/enginetest/doltgres_engine_test.go +++ b/testing/go/enginetest/doltgres_engine_test.go @@ -1252,6 +1252,7 @@ func TestBrokenSystemTableQueries(t *testing.T) { } func TestHistorySystemTable(t *testing.T) { + t.Skip() harness := newDoltgresServerHarness(t).WithParallelism(2) denginetest.RunHistorySystemTableTests(t, harness) }