snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
This commit is contained in:
96
internal/goldeneye/spanner/relations.go
Normal file
96
internal/goldeneye/spanner/relations.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package spanner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/goldeneye/dialect"
|
||||
)
|
||||
|
||||
// relationQuery lists the relations of every schema but the database's
|
||||
// own, unnamed one — INFORMATION_SCHEMA and SPANNER_SYS — with their
|
||||
// columns in declared order, so that the output is stable. A column's
|
||||
// type is spelled the way SPANNER_TYPE spells a declaration: STRING(MAX),
|
||||
// ARRAY<INT64>.
|
||||
const relationQuery = `
|
||||
SELECT t.TABLE_SCHEMA, t.TABLE_NAME, t.TABLE_TYPE,
|
||||
c.COLUMN_NAME, c.SPANNER_TYPE, c.IS_NULLABLE
|
||||
FROM INFORMATION_SCHEMA.TABLES AS t
|
||||
JOIN INFORMATION_SCHEMA.COLUMNS AS c
|
||||
ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME
|
||||
WHERE t.TABLE_SCHEMA != ''
|
||||
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME, c.ORDINAL_POSITION`
|
||||
|
||||
// readRelations reads the views of the system schemas. Their names are
|
||||
// kept as the catalog spells them, in upper case, which is how a query
|
||||
// names them; a column's type is spelled in lower case, the way the
|
||||
// dialect's types.jsonl spells its types, with an ARRAY<T> written as T
|
||||
// with the array flag, since that is how a seed spells an array.
|
||||
func readRelations(ctx context.Context, s *server, session string) ([]dialect.Relation, error) {
|
||||
rows, err := s.query(ctx, session, relationQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var relations []dialect.Relation
|
||||
var cur *dialect.Relation
|
||||
for _, row := range rows {
|
||||
if len(row) != 6 {
|
||||
return nil, fmt.Errorf("expected 6 columns, got %d", len(row))
|
||||
}
|
||||
schema, name, kind := row[0].GetStringValue(), row[1].GetStringValue(), row[2].GetStringValue()
|
||||
if cur == nil || cur.Schema != schema || cur.Name != name {
|
||||
rel := dialect.Relation{Schema: schema, Name: name}
|
||||
if kind == "VIEW" {
|
||||
rel.Kind = "v"
|
||||
}
|
||||
relations = append(relations, rel)
|
||||
cur = &relations[len(relations)-1]
|
||||
}
|
||||
col := dialect.Column{
|
||||
Name: row[3].GetStringValue(),
|
||||
NotNull: row[5].GetStringValue() == "NO",
|
||||
}
|
||||
col.Type, col.Array = typeName(row[4].GetStringValue())
|
||||
cur.Columns = append(cur.Columns, col)
|
||||
}
|
||||
return relations, nil
|
||||
}
|
||||
|
||||
// typeName spells a SPANNER_TYPE the way a seed spells a column's type:
|
||||
// in lower case, with an ARRAY<T> as its element and the array flag, a
|
||||
// STRUCT<a T, b U> as struct(a: t, b: u) and a PROTO<p.M> or ENUM<p.E> as
|
||||
// proto('p.M') or enum('p.E'), since a seed spells a type's arguments in
|
||||
// parentheses.
|
||||
func typeName(spannerType string) (string, bool) {
|
||||
t := strings.TrimSpace(spannerType)
|
||||
if element, ok := strings.CutPrefix(t, "ARRAY<"); ok && strings.HasSuffix(element, ">") {
|
||||
name, _ := typeName(strings.TrimSuffix(element, ">"))
|
||||
return name, true
|
||||
}
|
||||
if fields, ok := strings.CutPrefix(t, "STRUCT<"); ok && strings.HasSuffix(fields, ">") {
|
||||
var args []string
|
||||
for _, f := range splitTop(strings.TrimSuffix(fields, ">"), ',') {
|
||||
f = strings.TrimSpace(f)
|
||||
label, typ := "", f
|
||||
if i := strings.IndexAny(f, " \t"); i > 0 && !strings.ContainsAny(f[:i], "<(") {
|
||||
label, typ = f[:i], strings.TrimSpace(f[i+1:])
|
||||
}
|
||||
name, array := typeName(typ)
|
||||
if array {
|
||||
name += "[]"
|
||||
}
|
||||
if label != "" {
|
||||
name = label + ": " + name
|
||||
}
|
||||
args = append(args, name)
|
||||
}
|
||||
return "struct(" + strings.Join(args, ", ") + ")", false
|
||||
}
|
||||
for _, kind := range []string{"PROTO<", "ENUM<"} {
|
||||
if message, ok := strings.CutPrefix(t, kind); ok && strings.HasSuffix(message, ">") {
|
||||
return strings.ToLower(strings.TrimSuffix(kind, "<")) + "('" + strings.TrimSuffix(message, ">") + "')", false
|
||||
}
|
||||
}
|
||||
return strings.ToLower(t), false
|
||||
}
|
||||
Reference in New Issue
Block a user