blob: 0d1a6013f4d52fb9dd6a1e3bc481b91fc7618804 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// lsql support for representing an entity type as a SQL table.
//
// Each entity type (for example, a struct) corresponding to a single SQL
// table should implement the SqlData interface, specifying the table name,
// definition of all table columns and constraints (as a SqlTable), and
// mapping of entity instance data fields to columns.
//
// The SqlTable::Get*() methods generate SQL query/statement strings for
// creating the table and other common SQL operations over the specified
// type. Generated strings and string fragments are in prepared statement
// format (with question mark placeholders for data values). Default values
// and projection are not supported.
package lsql
import (
"strings"
)
// An entity type representable as a SQL table row.
// Should be implemented by each type to be persisted in a SQL table via lsql.
type SqlData interface {
// SQL table name (must be a valid SQL identifier).
// Return value must depend only on entity type, not entity instance.
TableName() string
// SQL table definition (refer to NewSqlTable).
// Return value must depend only on entity type, not entity instance.
TableDef() *SqlTable
// Array of references to entity instance data fields mapping to SQL columns,
// in the same order as specified in the table definition. Every column must
// have a corresponding reference of the appropriate type.
QueryRefs() []interface{}
}
//////////////////////////////////////////
// Table schema description
// Read-only specification of a SQL column.
type SqlColumn struct {
// Column name (must be a valid SQL identifier).
Name string
// Column SQL type, as specified in a CREATE TABLE statement.
Type string
// Whether the column is allowed to be NULL.
Null bool
}
// Initialize using NewSqlTable.
type SqlTable struct {
tableName string
columns []SqlColumn
constraints []string
}
// proto: Instance of the corresponding data entity.
// columns: SqlColumn specifications for each column.
// The first column is used as the primary key.
// constraints: Table constraints in SQL syntax (excluding PRIMARY KEY).
func NewSqlTable(proto SqlData, columns []SqlColumn, constraints []string) *SqlTable {
return &SqlTable{
tableName: proto.TableName(),
columns: columns,
constraints: constraints,
}
}
func (t *SqlTable) TableName() string {
return t.tableName
}
func (t *SqlTable) KeyName() string {
return t.columns[0].Name
}
// List of column names.
func (t *SqlTable) ColumnNames() []string {
colNames := make([]string, 0, len(t.columns))
for _, c := range t.columns {
colNames = append(colNames, c.Name)
}
return colNames
}
func (c *SqlColumn) getColumnSpec() string {
nullSpec := "NULL"
if !c.Null {
nullSpec = "NOT NULL"
}
return c.Name + " " + c.Type + " " + nullSpec
}
//////////////////////////////////////////
// Statement/query string generation
// SQL statement to create the table.
func (t *SqlTable) GetCreateTable(ifNotExists bool) string {
createCmd := "CREATE TABLE "
if ifNotExists {
createCmd += "IF NOT EXISTS "
}
clauses := make([]string, 0, len(t.columns)+1+len(t.constraints))
for _, c := range t.columns {
clauses = append(clauses, c.getColumnSpec())
}
clauses = append(clauses, "PRIMARY KEY ("+t.KeyName()+")")
clauses = append(clauses, t.constraints...)
return createCmd + t.TableName() + " ( " + strings.Join(clauses, ", ") + " )"
}
// Get* methods below return SQL query/statement strings with placeholders for
// common CRUD operations over the corresponding type.
// WHERE constraint fragment for matching primary key.
func (t *SqlTable) GetWhereKey() string {
return "(" + t.KeyName() + "=?)"
}
// SELECT query for entities matching WHERE clause. No projection.
func (t *SqlTable) GetSelectQuery(whereClause string) string {
query := "SELECT " + strings.Join(t.ColumnNames(), ",") + " FROM " + t.TableName()
return appendWhere(query, whereClause)
}
// COUNT query for entities matching WHERE clause.
func (t *SqlTable) GetCountQuery(whereClause string) string {
query := "SELECT COUNT(" + t.KeyName() + ") FROM " + t.TableName()
return appendWhere(query, whereClause)
}
// INSERT query for entity. Default values not supported, all columns mapped.
func (t *SqlTable) GetInsertQuery() string {
colNames := t.ColumnNames()
query := "INSERT INTO " + t.TableName() +
" (" + strings.Join(colNames, ",") + ")" +
" VALUES (?" + strings.Repeat(",?", len(colNames)-1) + ")"
return query
}
// UPDATE query for entity. Updates all columns (except the primary key).
func (t *SqlTable) GetUpdateQuery(whereClause string) string {
query := "UPDATE " + t.TableName() + " SET "
colUps := t.ColumnNames()[1:]
for i := range colUps {
colUps[i] += "=?"
}
query += strings.Join(colUps, ",")
return appendWhere(query, whereClause)
}
func appendWhere(query, whereClause string) string {
return query + " WHERE (" + whereClause + ")"
}