2020-08-06 20:58:47 +03:00
|
|
|
package source
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
|
2020-08-23 13:42:15 +03:00
|
|
|
"github.com/neilotoole/sq/libsq/core/errz"
|
|
|
|
"github.com/neilotoole/sq/libsq/core/kind"
|
2020-08-06 20:58:47 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Metadata holds metadata for a source.
|
|
|
|
type Metadata struct {
|
|
|
|
// Handle is the source handle.
|
|
|
|
Handle string `json:"handle"`
|
|
|
|
|
2023-01-01 06:17:44 +03:00
|
|
|
// Location is the source location such as a DB connection string,
|
|
|
|
// a file path, or a URL.
|
|
|
|
Location string `json:"location"`
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
// Name is the base name of the source, e.g. the base filename
|
|
|
|
// or DB name etc. For example, "sakila".
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
|
|
// FQName is the full name of the data source, typically
|
|
|
|
// including catalog/schema etc. For example, "sakila.public"
|
2020-08-16 00:06:40 +03:00
|
|
|
FQName string `json:"name_fq"`
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2023-03-14 04:25:12 +03:00
|
|
|
// Schema is the schema name, for example "public".
|
|
|
|
// This may be empty for some sources.
|
|
|
|
Schema string `json:"schema,omitempty"`
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
// SourceType is the source driver type.
|
|
|
|
SourceType Type `json:"driver"`
|
|
|
|
|
|
|
|
// DBDriverType is the type of the underling DB driver.
|
|
|
|
// This is the same value as SourceType for SQL database types.
|
|
|
|
DBDriverType Type `json:"db_driver"`
|
|
|
|
|
|
|
|
// DBProduct is the DB product string, such as "PostgreSQL 9.6.17 on x86_64-pc-linux-gnu".
|
|
|
|
DBProduct string `json:"db_product"`
|
|
|
|
|
|
|
|
// DBVersion is the DB version.
|
|
|
|
DBVersion string `json:"db_version"`
|
|
|
|
|
|
|
|
// User is the username, if applicable.
|
|
|
|
User string `json:"user,omitempty"`
|
|
|
|
|
2022-12-25 07:04:18 +03:00
|
|
|
// Size is the physical size of the source loc bytes, e.g. DB file size.
|
2020-08-06 20:58:47 +03:00
|
|
|
Size int64 `json:"size"`
|
|
|
|
|
2022-12-25 07:04:18 +03:00
|
|
|
// Tables is the metadata for each table loc the source.
|
2020-08-06 20:58:47 +03:00
|
|
|
Tables []*TableMetadata `json:"tables"`
|
2023-01-01 06:17:44 +03:00
|
|
|
|
|
|
|
// DBVars are configuration name-value pairs from the DB.
|
|
|
|
DBVars []DBVar `json:"db_variables,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-04-08 21:09:27 +03:00
|
|
|
// Table returns the named table, or nil.
|
|
|
|
func (md *Metadata) Table(tblName string) *TableMetadata {
|
|
|
|
if md == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tbl := range md.Tables {
|
|
|
|
if tbl.Name == tblName {
|
|
|
|
return tbl
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-01 06:17:44 +03:00
|
|
|
// Clone returns a deep copy of md. If md is nil, nil is returned.
|
|
|
|
func (md *Metadata) Clone() *Metadata {
|
|
|
|
if md == nil {
|
|
|
|
return md
|
|
|
|
}
|
|
|
|
|
|
|
|
c := &Metadata{
|
|
|
|
Handle: md.Handle,
|
|
|
|
Location: md.Location,
|
|
|
|
Name: md.Name,
|
|
|
|
FQName: md.FQName,
|
|
|
|
SourceType: md.SourceType,
|
|
|
|
DBDriverType: md.DBDriverType,
|
|
|
|
DBProduct: md.DBProduct,
|
|
|
|
DBVersion: md.DBVersion,
|
|
|
|
User: md.User,
|
|
|
|
Size: md.Size,
|
|
|
|
Tables: nil,
|
|
|
|
DBVars: nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
if md.DBVars != nil {
|
|
|
|
copy(c.DBVars, md.DBVars)
|
|
|
|
}
|
|
|
|
|
|
|
|
if md.Tables != nil {
|
|
|
|
c.Tables = make([]*TableMetadata, len(md.Tables))
|
|
|
|
for i := range md.Tables {
|
|
|
|
c.Tables[i] = md.Tables[i].Clone()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return c
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// TableNames is a convenience method that returns md's table names.
|
|
|
|
func (md *Metadata) TableNames() []string {
|
2021-09-19 06:37:22 +03:00
|
|
|
names := make([]string, len(md.Tables))
|
2021-09-13 01:14:30 +03:00
|
|
|
for i, tblDef := range md.Tables {
|
|
|
|
names[i] = tblDef.Name
|
2020-08-06 20:58:47 +03:00
|
|
|
}
|
|
|
|
return names
|
|
|
|
}
|
|
|
|
|
|
|
|
func (md *Metadata) String() string {
|
|
|
|
bytes, _ := json.Marshal(md)
|
|
|
|
return string(bytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DBVar models a key-value pair for driver config.
|
|
|
|
type DBVar struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Value string `json:"value"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// TableMetadata models table (or view) metadata.
|
|
|
|
type TableMetadata struct {
|
|
|
|
// Name is the table name, such as "actor".
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
|
|
// FQName is the fully-qualified name, such as "sakila.public.actor"
|
2020-08-16 00:06:40 +03:00
|
|
|
FQName string `json:"name_fq,omitempty"`
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2020-08-16 00:06:40 +03:00
|
|
|
// TableType indicates if this is a "table" or "view". The value
|
|
|
|
// is driver-independent. See DBTableType for the driver-dependent
|
|
|
|
// value.
|
|
|
|
TableType string `json:"table_type,omitempty"`
|
|
|
|
|
|
|
|
// DBTableType indicates if this is a table or view, etc.
|
|
|
|
// The value is driver-dependent, e.g. "BASE TABLE" or "VIEW" for postgres.
|
|
|
|
DBTableType string `json:"table_type_db,omitempty"`
|
2020-08-06 20:58:47 +03:00
|
|
|
|
2022-12-25 07:04:18 +03:00
|
|
|
// RowCount is the number of rows loc the table.
|
2020-08-06 20:58:47 +03:00
|
|
|
RowCount int64 `json:"row_count"`
|
|
|
|
|
2022-12-25 07:04:18 +03:00
|
|
|
// Size is the physical size of the table loc bytes. For a view, this
|
2020-08-16 00:06:40 +03:00
|
|
|
// may be nil.
|
|
|
|
Size *int64 `json:"size,omitempty"`
|
2020-08-06 20:58:47 +03:00
|
|
|
|
|
|
|
// Comment is the comment for the table. Typically empty.
|
|
|
|
Comment string `json:"comment,omitempty"`
|
|
|
|
|
|
|
|
// Columns holds the metadata for the table's columns.
|
|
|
|
Columns []*ColMetadata `json:"columns"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TableMetadata) String() string {
|
|
|
|
bytes, _ := json.Marshal(t)
|
|
|
|
return string(bytes)
|
|
|
|
}
|
|
|
|
|
2023-01-01 06:17:44 +03:00
|
|
|
// Clone returns a deep copy of t. If t is nil, nil is returned.
|
|
|
|
func (t *TableMetadata) Clone() *TableMetadata {
|
|
|
|
if t == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
c := &TableMetadata{
|
|
|
|
Name: t.Name,
|
|
|
|
FQName: t.FQName,
|
|
|
|
TableType: t.TableType,
|
|
|
|
DBTableType: t.DBTableType,
|
|
|
|
RowCount: t.RowCount,
|
|
|
|
Size: t.Size,
|
|
|
|
Comment: t.Comment,
|
|
|
|
Columns: nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.Columns != nil {
|
|
|
|
c.Columns = make([]*ColMetadata, len(t.Columns))
|
|
|
|
for i := range t.Columns {
|
|
|
|
c.Columns[i] = t.Columns[i].Clone()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
// Column returns the named col or nil.
|
|
|
|
func (t *TableMetadata) Column(colName string) *ColMetadata {
|
|
|
|
for _, col := range t.Columns {
|
|
|
|
if col.Name == colName {
|
|
|
|
return col
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PKCols returns a possibly empty slice of cols that are part
|
|
|
|
// of the table primary key.
|
|
|
|
func (t *TableMetadata) PKCols() []*ColMetadata {
|
|
|
|
var pkCols []*ColMetadata
|
|
|
|
for _, col := range t.Columns {
|
|
|
|
if col.PrimaryKey {
|
|
|
|
pkCols = append(pkCols, col)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return pkCols
|
|
|
|
}
|
|
|
|
|
|
|
|
// ColMetadata models metadata for a particular column of a data source.
|
|
|
|
type ColMetadata struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Position int64 `json:"position"`
|
|
|
|
PrimaryKey bool `json:"primary_key"`
|
|
|
|
BaseType string `json:"base_type"`
|
|
|
|
ColumnType string `json:"column_type"`
|
2020-08-23 13:42:15 +03:00
|
|
|
Kind kind.Kind `json:"kind"`
|
2020-08-06 20:58:47 +03:00
|
|
|
Nullable bool `json:"nullable"`
|
|
|
|
DefaultValue string `json:"default_value,omitempty"`
|
|
|
|
Comment string `json:"comment,omitempty"`
|
|
|
|
// TODO: Add foreign key field
|
|
|
|
}
|
|
|
|
|
2023-01-01 06:17:44 +03:00
|
|
|
// Clone returns a deep copy of c. If c is nil, nil is returned.
|
|
|
|
func (c *ColMetadata) Clone() *ColMetadata {
|
|
|
|
if c == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ColMetadata{
|
|
|
|
Name: c.Name,
|
|
|
|
Position: c.Position,
|
|
|
|
PrimaryKey: c.PrimaryKey,
|
|
|
|
BaseType: c.BaseType,
|
|
|
|
ColumnType: c.ColumnType,
|
|
|
|
Kind: c.Kind,
|
|
|
|
Nullable: c.Nullable,
|
|
|
|
DefaultValue: c.DefaultValue,
|
|
|
|
Comment: c.Comment,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 20:58:47 +03:00
|
|
|
func (c *ColMetadata) String() string {
|
|
|
|
bytes, _ := json.Marshal(c)
|
|
|
|
return string(bytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TableFromSourceMetadata returns TableMetadata whose name matches
|
|
|
|
// tblName.
|
|
|
|
//
|
|
|
|
// Deprecated: Each driver should implement this correctly for a single table.
|
|
|
|
func TableFromSourceMetadata(srcMeta *Metadata, tblName string) (*TableMetadata, error) {
|
|
|
|
for _, tblMeta := range srcMeta.Tables {
|
|
|
|
if tblMeta.Name == tblName {
|
|
|
|
return tblMeta, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, errz.Errorf("metadata for table %s not found", tblName)
|
|
|
|
}
|