/
githubmirror
/
sqlc
Обзор
Документация
Войти
/
githubmirror
/
sqlc
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
internal/tools/sqlc-pg-gen/main.go
431 строка
9 KB
Kyle Gray
Remove github.com/jackc/pgx/v4 dependency (#4379)
17 апр 2026, 07:41
Не верифицирован
17 апр 2026, 07:41
93e1a17
Код
Авторство
О чём код?
package main import ( "bytes" "context" "flag" "fmt" "go/format" "log" "os" "path/filepath" "sort" "strings" "text/template" "github.com/jackc/pgx/v5" ) // https://dba.stackexchange.com/questions/255412/how-to-select-functions-that-belong-in-a-given-extension-in-postgresql // // Extension functions are added to the public schema const extensionFuncs = ` WITH extension_funcs AS ( SELECT p.oid FROM pg_catalog.pg_extension AS e INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid) INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid) INNER JOIN pg_catalog.pg_namespace AS ne ON (ne.oid = e.extnamespace) INNER JOIN pg_catalog.pg_namespace AS np ON (np.oid = p.pronamespace) WHERE d.deptype = 'e' AND e.extname = $1 ) SELECT p.proname as name, format_type(p.prorettype, NULL), array(select format_type(unnest(p.proargtypes), NULL)), p.proargnames, p.proargnames[p.pronargs-p.pronargdefaults+1:p.pronargs], p.proargmodes::text[] FROM pg_catalog.pg_proc p JOIN extension_funcs ef ON ef.oid = p.oid WHERE pg_function_is_visible(p.oid) -- simply order all columns to keep subsequent runs stable ORDER BY 1, 2, 3, 4, 5; ` const catalogTmpl = ` // Code generated by sqlc-pg-gen. DO NOT EDIT. package {{.Pkg}} import ( "github.com/sqlc-dev/sqlc/internal/sql/ast" "github.com/sqlc-dev/sqlc/internal/sql/catalog" ) var funcs{{.GenFnName}} = []*catalog.Function { {{- range .Procs}} { Name: "{{.Name}}", Args: []*catalog.Argument{ {{range .Args}}{ {{- if .Name}} Name: "{{.Name}}", {{- end}} {{- if .HasDefault}} HasDefault: true, {{- end}} Type: &ast.TypeName{Name: "{{.TypeName}}"}, {{- if ne .Mode "i" }} Mode: {{ .GoMode }}, {{- end}} }, {{end}} }, ReturnType: &ast.TypeName{Name: "{{.ReturnTypeName}}"}, }, {{- end}} } func {{.GenFnName}}() *catalog.Schema { s := &catalog.Schema{Name: "{{ .SchemaName }}"} s.Funcs = funcs{{.GenFnName}} {{- if .Relations }} s.Tables = []*catalog.Table { {{- range .Relations }} { Rel: &ast.TableName{ Catalog: "{{.Catalog}}", Schema: "{{.SchemaName}}", Name: "{{.Name}}", }, Columns: []*catalog.Column{ {{- range .Columns}} { Name: "{{.Name}}", Type: ast.TypeName{Name: "{{.Type}}"}, {{- if .IsNotNull}} IsNotNull: true, {{- end}} {{- if .IsArray}} IsArray: true, {{- end}} {{- if .Length }} Length: toPointer({{ .Length }}), {{- end}} }, {{- end}} }, }, {{- end}} } {{- end }} return s } ` const loaderFuncTmpl = ` // Code generated by sqlc-pg-gen. DO NOT EDIT. package postgresql import ( "github.com/sqlc-dev/sqlc/internal/engine/postgresql/contrib" "github.com/sqlc-dev/sqlc/internal/sql/catalog" ) func loadExtension(name string) *catalog.Schema { switch name { {{- range .}} case "{{.Name}}": return contrib.{{.Func}}() {{- end}} } return nil } ` type tmplCtx struct { Pkg string GenFnName string SchemaName string Procs []Proc Relations []Relation } func main() { if err := run(context.Background()); err != nil { log.Fatal(err) } } func clean(arg string) string { arg = strings.TrimSpace(arg) arg = strings.Replace(arg, "\"any\"", "any", -1) arg = strings.Replace(arg, "\"char\"", "char", -1) arg = strings.Replace(arg, "\"timestamp\"", "char", -1) return arg } // writeFormattedGo executes `tmpl` with `data` as its context to the file `destPath` func writeFormattedGo(tmpl *template.Template, data any, destPath string) error { out := bytes.NewBuffer([]byte{}) err := tmpl.Execute(out, data) if err != nil { return err } code, err := format.Source(out.Bytes()) if err != nil { return err } err = os.WriteFile(destPath, code, 0644) if err != nil { return err } return nil } // preserveLegacyCatalogBehavior maintain previous ordering and filtering // that was manually done to the generated file pg_catalog.go. // Some of the test depend on this ordering - in particular, function lookups // where there might be multiple matching functions (due to type overloads) // Until sqlc supports "smarter" looking up of these functions, // preserveLegacyCatalogBehavior ensures there are no accidental test breakages func preserveLegacyCatalogBehavior(allProcs []Proc) []Proc { // Preserve the legacy sort order of the end-to-end tests sort.SliceStable(allProcs, func(i, j int) bool { fnA := allProcs[i] fnB := allProcs[j] if fnA.Name == "lower" && fnB.Name == "lower" && len(fnA.ArgTypes) == 1 && fnA.ArgTypes[0] == "text" { return true } if fnA.Name == "generate_series" && fnB.Name == "generate_series" && len(fnA.ArgTypes) == 2 && fnA.ArgTypes[0] == "numeric" { return true } return false }) procs := make([]Proc, 0, len(allProcs)) for _, p := range allProcs { // Skip generating pg_catalog.concat to preserve legacy behavior if p.Name == "concat" { continue } procs = append(procs, p) } return procs } func databaseURL() string { dburl := os.Getenv("DATABASE_URL") if dburl != "" { return dburl } pgUser := os.Getenv("PG_USER") pgHost := os.Getenv("PG_HOST") pgPort := os.Getenv("PG_PORT") pgPass := os.Getenv("PG_PASSWORD") pgDB := os.Getenv("PG_DATABASE") if pgUser == "" { pgUser = "postgres" } if pgPass == "" { pgPass = "mysecretpassword" } if pgPort == "" { pgPort = "5432" } if pgHost == "" { pgHost = "127.0.0.1" } if pgDB == "" { pgDB = "dinotest" } return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", pgUser, pgPass, pgHost, pgPort, pgDB) } func run(ctx context.Context) error { flag.Parse() dir := flag.Arg(0) if dir == "" { dir = filepath.Join("internal", "engine", "postgresql") } tmpl, err := template.New("").Parse(catalogTmpl) if err != nil { return err } conn, err := pgx.Connect(ctx, databaseURL()) if err != nil { return err } defer conn.Close(ctx) schemas := []schemaToLoad{ { Name: "pg_catalog", GenFnName: "genPGCatalog", DestPath: filepath.Join(dir, "pg_catalog.go"), }, { Name: "information_schema", GenFnName: "genInformationSchema", DestPath: filepath.Join(dir, "information_schema.go"), }, } for _, schema := range schemas { procs, err := readProcs(ctx, conn, schema.Name) if err != nil { return err } if schema.Name == "pg_catalog" { procs = preserveLegacyCatalogBehavior(procs) } relations, err := readRelations(ctx, conn, schema.Name) if err != nil { return err } err = writeFormattedGo(tmpl, tmplCtx{ Pkg: "postgresql", SchemaName: schema.Name, GenFnName: schema.GenFnName, Procs: procs, Relations: relations, }, schema.DestPath) if err != nil { return err } } loaded := []extensionPair{} for _, extension := range extensions { name := strings.Replace(extension, "-", "_", -1) var funcName string for _, part := range strings.Split(name, "_") { funcName += strings.Title(part) } if _, err := conn.Exec(ctx, fmt.Sprintf("CREATE EXTENSION IF NOT EXISTS %q", extension)); err != nil { return fmt.Errorf("error creating %s: %s", extension, err) } rows, err := conn.Query(ctx, extensionFuncs, extension) if err != nil { return err } procs, err := scanProcs(rows) if err != nil { return err } if len(procs) == 0 { log.Printf("no functions in %s, skipping", extension) continue } // Preserve the legacy sort order of the end-to-end tests sort.SliceStable(procs, func(i, j int) bool { fnA := procs[i] fnB := procs[j] if extension == "pgcrypto" { if fnA.Name == "digest" && fnB.Name == "digest" && len(fnA.ArgTypes) == 2 && fnA.ArgTypes[0] == "text" { return true } } return false }) extensionPath := filepath.Join(dir, "contrib", name+".go") err = writeFormattedGo(tmpl, tmplCtx{ Pkg: "contrib", SchemaName: "pg_catalog", GenFnName: funcName, Procs: procs, }, extensionPath) if err != nil { return fmt.Errorf("error generating extension %s: %w", extension, err) } loaded = append(loaded, extensionPair{Name: extension, Func: funcName}) } extensionTmpl, err := template.New("").Parse(loaderFuncTmpl) if err != nil { return err } extensionLoaderPath := filepath.Join(dir, "extension.go") err = writeFormattedGo(extensionTmpl, loaded, extensionLoaderPath) if err != nil { return err } return nil } type schemaToLoad struct { // name is the name of a schema to load Name string // DestPath is the desination for the generate file DestPath string // The name of the function to generate for loading this schema GenFnName string } type extensionPair struct { Name string Func string } // https://www.postgresql.org/docs/current/contrib.html var extensions = []string{ "adminpack", "amcheck", // "auth_delay", // "auto_explain", // "bloom", "btree_gin", "btree_gist", "citext", "cube", "dblink", // "dict_int", // "dict_xsyn", "earthdistance", "file_fdw", "fuzzystrmatch", "hstore", "intagg", "intarray", "isn", "lo", "ltree", "pageinspect", // "passwordcheck", "pg_buffercache", "pg_freespacemap", "pg_prewarm", "pg_stat_statements", "pg_trgm", "pg_visibility", "pgcrypto", "pgrowlocks", "pgstattuple", "postgres_fdw", "seg", // "sepgsql", // "spi", "sslinfo", "tablefunc", "tcn", // "test_decoding", // "tsm_system_rows", // "tsm_system_time", "unaccent", "uuid-ossp", "xml2", }