Files
upstream-mirror 0f4df23290
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
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

97 lines
3.4 KiB
Go

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
}