sqac

package module
Version: v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 17, 2019 License: MIT Imports: 13 Imported by: 0

README

sqac

Sqac is a simple overlay to provide a common interface to an attached mssql, mysql, postgres, sqlite or SAP Hana database.

  • create tables, supporting default, nullable, start, primary-key, index tags
  • drop tables
  • destructive reset of tables
  • create indexes
  • drop indexes
  • alter tables via column, index and sequence additions
  • set sequence, auto-increment or identity nextval
  • Standard go sql, jmoirons sqlx db access
  • generic CRUD entity operations
  • UTC timestamps used internally for all time types
  • set commands (/$count /$orderby=<field_name> $limit=n; $offset=n; ($asc|$desc))
  • comprehensive test cases

Outstanding TODO's

  • update for modules support
  • refactor non-idempotent SQLite Foreign-Key test to use a closure
  • consider parsing the stored create schema when adding / dropping a foreign-key on SQLite tables
  • add cascade to Drops?
  • examine the $desc orderby when limit / offset is used in postgres with selection parameter (odd)
  • change from timestamp with TZ to timestamp and ensure timestamps are in UTC before submitting to the db
  • examine view support
  • remove extraneous getSet-type methods
  • ProcessSchema does not return an error; ProcessTransaction does? Noticed this in DropIndex. Inconsistent.
  • Support unique constraints on grouped fields(?)
  • Consider converting all time reads as Local
  • HDB ExistsTable should include SCHEMA field in selection?
  • It would be nice to replace the fmt.Sprintf(...) calls in the DDL and DML constructions with inline strconv.XXXX. In practical terms we are dealing with 10's of ns here, but it could be a thing. Consider doing this when implementing DB2 support.

Installation

Install sqac via go get:

go get -u github.com/sqac

Ensure that you have also installed the drivers for the databases you plan to use. Supported drivers include:
Driver Name Driver Location
SAP Hana Database Driver github.com/SAP/go-hdb/driver
MSSQL Database Driver github.com/denisenkom/go-mssqldb
MySQL Database Driver github.com/go-sql-driver/mysql
PostgreSQL Database Driver github.com/lib/pq
SQLite3 Database Driver github.com/mattn/go-sqlite3

Verify the installation by running the included test suite against sqlite. Test execution will create a 'testdb.sqlite' database file in the sqac directory. The tests are not entirely idempotent and the testdb.sqlite file will not be cleaned up. This is by design as the tests were used for debugging purposes during the development. It would be a simple matter to tidy this up.
go test -v -db sqlite

If testing against sqlite is not an option, the test suite may be run against any of the supported database systems. When running against a non-sqlite db, a connection string must be supplied via the cs flag. See the Connection Strings section for database-specific connection string formats.

go test -v -db pg -cs "host=127.0.0.1 user=my_uname dbname=my_dbname sslmode=disable password=my_passwd"

Running Tests

go test -v -db <dbtype>
Postgres
go test -v -db postgres -cs "host=127.0.0.1 user=my_uname dbname=my_dbname sslmode=disable password=my_passwd"
MySQL
go test -v -db mysql -cs "my_uname:my_passwd@tcp(192.168.1.10:3306)/my_dbname?charset=utf8&parseTime=True&loc=Local"
MSSQL
go test -v -db mssql -cs "sqlserver://SA:my_passwd@localhost:1401?database=my_dbname"
SQLite
go test -v -db sqlite
SAP Hana
go test -v -db hdb "hdb://my_uname:my_passwd@192.168.111.45:30015"


Connection Strings

Sqac presently supports MSSQL, MySQL, PostgreSQL, Sqlite3 and the SAP Hana database. You will need to know the db user-name / password, as well as the address:port and name of the database.

MSSQL Connection String
cs := "sqlserver://SA:my_passwd@localhost:1401?database=my_dbname"
MySQL Connection String
cs := "my_uname:my_passwd@tcp(192.168.1.10:3306)/my_dbname?charset=utf8&parseTime=True&loc=Local"
PostgreSQL Connection String
cs := "host=127.0.0.1 user=my_uname dbname=my_dbname sslmode=disable password=my_passwd"
Sqlite3 Connection String
cs := "my_db_file.sqlite"

// or

cs = "my_db_file.db"
SAP Hana Connection String
cs := "hdb://my_uname:my_passwd@192.168.111.45:30015"

Quickstart

The following example illustrates the general usage of the sqac library.

package main

import (
  "flag"
  "fmt"

  "github.com/1414C/sqac"
  // "github.com/1414C/sqac/common"
  _ "github.com/SAP/go-hdb/driver"
  _ "github.com/denisenkom/go-mssqldb"
  _ "github.com/go-sql-driver/mysql"
  _ "github.com/lib/pq"
  _ "github.com/mattn/go-sqlite3"
)

func main() {

  // valid dbFlag values: {hdb, sqlite, mssql, mysql, postgres}
  dbFlag := flag.String("db", "sqlite", "db-type for connection")
  // see ConnectionStrings in this document for valid csFlag value formats
  csFlag := flag.String("cs", "testdb.sqlite", "connection-string for the database")
  // the logging is verbose and targetted at debugging
  logFlag := flag.Bool("l", false, "activate sqac detail logging to stdout")
  // the db logging provides a close approximation to the commands issued to the db
  dbLogFlag := flag.Bool("dbl", false, "activate DDL/DML logging to stdout)")
  flag.Parse()

  // This will be the central access-point to the ORM and should be made
  // available in all locations where access to the persistent storage
  // (database) is required.
  var (
    Handle sqac.PublicDB
  )

  // Declare a struct to use as a source for table declaration.
  type Depot struct {
      DepotNum   int       `db:"depot_num" sqac:"primary_key:inc"`
      CreateDate time.Time `db:"create_date" sqac:"nullable:false;default:now();"`
      Region     string    `db:"region" sqac:"nullable:false;default:YYC"`
      Province   string    `db:"province" sqac:"nullable:false;default:AB"`
      Country    string    `db:"country" sqac:"nullable:false;default:CA"`
  }

  // Create a PublicDB instance.  Check the Create method, as the return parameter contains
  // not only an implementation of PublicDB targeting the db-type/db, but also a pointer
  // facilitating access to the db via jmoiron's sqlx package.  This is useful if you wish
  // to access the sql/sqlx APIs directly.
  Handle = sqac.Create(*dbFlag, *logFlag, *dbLogFlag, *cs)

  // Execute a call to get the name of the db-driver being used.  At this point, any method
  // contained in the sqac.PublicDB interface may be called.
  driverName := Handle.GetDBDriverName()
  fmt.Println("driverName:", driverName)

  // Create a new table in the database
  err := Handle.CreateTables(Depot{})
  if err != nil {
    t.Errorf("%s", err.Error())
  }

  // Determine the table name as per the table creation logic
  tn := common.GetTableName(Depot{})

  // Expect that table depot exists
  if !Handle.ExistsTable(tn) {
    t.Errorf("table %s was not created", tn)
  }

  // Drop the table
  err = Handle.DropTables(Depot{})
  if err != nil {
    t.Errorf("table %s was not dropped", tn)
  }

  // Close the connection.
  Handle.Close()
}

Execute the sample program as follows using sqlite. Note that the sample program makes no effort to validate the flag parameters.

go run -db sqlite -cs testdb.sqlite main.go

Table Declaration Using Nested Structs

Table declarations may also contain nested structs:

type Triplet struct {
  TripOne   string `db:"trip_one" sqac:"nullable:false"`
  TripTwo   int64  `db:"trip_two" sqac:"nullable:false;default:0"`
  Tripthree string `db:"trip_three" sqac:"nullable:false"`
}

type Equipment struct {
  EquipmentNum   int64     `db:"equipment_num" sqac:"primary_key:inc;start:55550000"`
  ValidFrom      time.Time `db:"valid_from" sqac:"primary_key;nullable:false;default:now()"`
  ValidTo        time.Time `db:"valid_to" sqac:"primary_key;nullable:false;default:make_timestamptz(9999, 12, 31, 23, 59, 59.9)"`
  CreatedAt      time.Time `db:"created_at" sqac:"nullable:false;default:now()"`
  InspectionAt   time.Time `db:"inspection_at" sqac:"nullable:true"`
  MaterialNum    int       `db:"material_num" sqac:"index:idx_material_num_serial_num"`
  Description    string    `db:"description" sqac:"sqac:nullable:false"`
  SerialNum      string    `db:"serial_num" sqac:"index:idx_material_num_serial_num"`
  Triplet        // structs can be nested to any level
}

Accessing Documentation

The API is somewhat documented via comments in the code. This can be accessed by running the godoc command:

godoc -http=:6061

Once the godoc server has started, hit http://localhost:6061/pkg/github.com/1414C/sqac/ for sqac API documentation.

Credits

Sqac makes use of

Documentation

Index

Constants

View Source
const CBackTick = "`"
View Source
const CDblQuote = "\""
View Source
const CTick = "'"

CTick CBackTick and CDblQuote specify the quote style for for db field encapsulation in CREATE and ALTER table schemas

Variables

This section is empty.

Functions

func Open

func Open(flavor string, args ...interface{}) (db *sqlx.DB, err error)

Open a sqlx connection to the specified database

Types

type BaseFlavor

type BaseFlavor struct {
	PublicDB
	// contains filtered or unexported fields
}

BaseFlavor is a supporting struct for interface PublicDB

func (*BaseFlavor) AlterSequenceStart

func (bf *BaseFlavor) AlterSequenceStart(name string, start int) error

AlterSequenceStart may be used to make changes to the start value of the named sequence, autoincrement or identity field depending on the manner in which the currently connected database flavour handles key generation.

func (*BaseFlavor) AlterTables

func (bf *BaseFlavor) AlterTables(i ...interface{}) error

AlterTables alters tables on the db based on the provided list of go struct definitions.

func (*BaseFlavor) BoolToDBBool

func (bf *BaseFlavor) BoolToDBBool(b bool) *int

BoolToDBBool converts a go-bool value into the DB bool representation. called for DB's that do not support a true/false boolean type.

func (*BaseFlavor) BuildComponents

func (bf *BaseFlavor) BuildComponents(inf *CrudInfo) error

BuildComponents is used by each flavor to assemble the struct (entity) data for CRUD operations. There is some redundancy in the structure for now, as it has recently been migrated into BaseFlavor.

func (*BaseFlavor) Close

func (bf *BaseFlavor) Close() error

Close closes the db-connection

func (*BaseFlavor) CreateForeignKey

func (bf *BaseFlavor) CreateForeignKey(i interface{}, ft, rt, ff, rf string) error

CreateForeignKey creates a foreign-key on an existing column.

func (*BaseFlavor) CreateIndex

func (bf *BaseFlavor) CreateIndex(in string, index IndexInfo) error

CreateIndex creates the index contained in the incoming IndexInfo structure. indexes will be created as non-unique by default, and in multi-field situations, the fields will added to the index in the order they are contained in the IndexInfo.[]IndexFields slice.

func (*BaseFlavor) CreateSequence

func (bf *BaseFlavor) CreateSequence(sn string, start int)

CreateSequence may be used to create a new sequence on the currently connected database.

func (*BaseFlavor) CreateTables

func (bf *BaseFlavor) CreateTables(i ...interface{}) error

CreateTables creates tables on the db based on the provided list of go struct definitions.

func (*BaseFlavor) DBBoolToBool

func (bf *BaseFlavor) DBBoolToBool(i interface{}) bool

DBBoolToBool converts from the DB representation of a bool into the go-bool type. The is called for DB's that do not support a true/false boolean type.

func (*BaseFlavor) DBLog

func (bf *BaseFlavor) DBLog(b bool)

DBLog sets the db-access-logging status

func (*BaseFlavor) Delete

func (bf *BaseFlavor) Delete(ent interface{}) error

Delete - CRUD Delete an existing entity (single-row) on the database using the full-key

func (*BaseFlavor) DestructiveResetTables

func (bf *BaseFlavor) DestructiveResetTables(i ...interface{}) error

DestructiveResetTables drops tables on the db if they exist, as well as any related objects such as sequences. this is useful if you wish to regenerated your table and the number-range used by an auto-incementing primary key.

func (*BaseFlavor) DropForeignKey

func (bf *BaseFlavor) DropForeignKey(i interface{}, ft, fkn string) error

DropForeignKey drops a foreign-key on an existing column

func (*BaseFlavor) DropIndex

func (bf *BaseFlavor) DropIndex(tn string, in string) error

DropIndex drops the specfied index on the connected database.

func (*BaseFlavor) DropSequence

func (bf *BaseFlavor) DropSequence(sn string) error

DropSequence may be used to drop the named sequence on the currently connected database. This is probably not needed, as we are now creating sequences on postgres in a more correct manner. select pg_get_serial_sequence('public.some_table', 'some_column');

func (*BaseFlavor) DropTables

func (bf *BaseFlavor) DropTables(i ...interface{}) error

DropTables drops tables on the db if they exist, based on the provided list of go struct definitions.

func (*BaseFlavor) Exec

func (bf *BaseFlavor) Exec(queryString string, args ...interface{}) (sql.Result, error)

Exec runs the queryString against the connected db

func (*BaseFlavor) ExecuteQuery

func (bf *BaseFlavor) ExecuteQuery(queryString string, qParams ...interface{}) (*sql.Rows, error)

ExecuteQuery processes the multi-row query contained in queryString against the connected DB using sql/database.

func (*BaseFlavor) ExecuteQueryRow

func (bf *BaseFlavor) ExecuteQueryRow(queryString string, qParams ...interface{}) *sql.Row

ExecuteQueryRow processes the single-row query contained in queryString against the connected DB using sql/database.

func (*BaseFlavor) ExecuteQueryRowx

func (bf *BaseFlavor) ExecuteQueryRowx(queryString string, qParams ...interface{}) *sqlx.Row

ExecuteQueryRowx processes the single-row query contained in queryString against the connected DB using sqlx.

func (*BaseFlavor) ExecuteQueryx

func (bf *BaseFlavor) ExecuteQueryx(queryString string, qParams ...interface{}) (*sqlx.Rows, error)

ExecuteQueryx processes the multi-row query contained in queryString against the connected DB using sqlx.

func (*BaseFlavor) ExistsColumn

func (bf *BaseFlavor) ExistsColumn(tn string, cn string) bool

ExistsColumn checks the currently connected database and returns true if the named table-column is found to exist. this checks the column name only, not the column data-type or properties.

func (*BaseFlavor) ExistsForeignKeyByFields

func (bf *BaseFlavor) ExistsForeignKeyByFields(i interface{}, ft, rt, ff, rf string) (bool, error)

ExistsForeignKeyByFields checks to see if a foreign-key exists between the named tables and fields.

func (*BaseFlavor) ExistsForeignKeyByName

func (bf *BaseFlavor) ExistsForeignKeyByName(i interface{}, fkn string) (bool, error)

ExistsForeignKeyByName checks to see if the named foreign-key exists on the table corresponding to provided sqac model (i).

func (*BaseFlavor) ExistsIndex

func (bf *BaseFlavor) ExistsIndex(tn string, in string) bool

ExistsIndex checks the connected database for the presence of the specified index.

func (*BaseFlavor) ExistsSequence

func (bf *BaseFlavor) ExistsSequence(sn string) bool

ExistsSequence checks for the presence of the named sequence on the currently connected database.

func (*BaseFlavor) ExistsTable

func (bf *BaseFlavor) ExistsTable(tn string) bool

ExistsTable checks the currently connected database and returns true if the named table is found to exist.

func (*BaseFlavor) Get

func (bf *BaseFlavor) Get(dst interface{}, queryString string, args ...interface{}) error

Get reads a single row into the dst interface. This calls sqlx.Get(...)

func (*BaseFlavor) GetDB

func (bf *BaseFlavor) GetDB() *sqlx.DB

GetDB returns a *sqlx.DB pointer if one has been set in the db-flavor environment.

func (*BaseFlavor) GetDBDriverName

func (bf *BaseFlavor) GetDBDriverName() string

GetDBDriverName returns the name of the current db-driver

func (*BaseFlavor) GetDBName

func (bf *BaseFlavor) GetDBName() (dbName string)

GetDBName returns the name of the currently connected db

func (*BaseFlavor) GetDBQuote

func (bf *BaseFlavor) GetDBQuote() string

GetDBQuote reports the quoting preference for db-query construction. this does not refer to quoted strings, but rather to the older(?) style of quoting table field-names in query-strings such as: SELECT "f1" FROM "t1" WHERE "v1" = <some_criteria>. in practice, it seems you can get away without quoting, but it is a nod to backward compatibility for existing db installs. ' vs ` vs " for example

func (*BaseFlavor) GetEntities

func (bf *BaseFlavor) GetEntities(ents interface{}) (interface{}, error)

GetEntities is experimental - use GetEntitiesWithCommands.

func (*BaseFlavor) GetEntities2

func (bf *BaseFlavor) GetEntities2(ge GetEnt) error

GetEntities2 attempts to retrieve all entities based on the internal implementation of interface GetEnt. GetEnt exposes a single method (Exec(handle PublicDB)error) to execute the request. All this because go can only go so far with meta-type programming in go before being buried in reflection. ge allows you to pass a sqac handle into the method so you can code directly against the desired struct/table with no reflection. GetEntities2 has been replaced by GetEntitiesWithCommands, but can be used if you want a clean looking API that is pretty quick (very light use of reflection). That said, it is a --dirty-- way of doing things.

func (*BaseFlavor) GetEntities4

func (bf *BaseFlavor) GetEntities4(ents interface{})

GetEntities4 is experimental - use GetEntitiesCP.

This method uses alot of reflection to permit the retrieval of the equivalent of []interface{} where interface{} can be taken to mean Model{}. This can be used, but may prove to be a slow way of doing things. A quick internet search on []interface{} will turn up all sorts of acrimony. Notice that the method signature is still interface{}? Not very transparent.

func (*BaseFlavor) GetEntitiesCP

func (bf *BaseFlavor) GetEntitiesCP(ents interface{}, pList []GetParam, cmdMap map[string]interface{}) (result uint64, err error)

GetEntitiesCP uses alot of reflection to permit the retrieval of the equivalent of []interface{} where interface{} can be taken to mean Model{}. This is the Get method that is preferred, as the caller can simply pass a pointer to a slice of the requested table-type in the ents parameter, then read the resulting slice directly in their program following method execution. Each DB needs slightly different handling due to differences in OFFSET / LIMIT / TOP support. This is a mostly common version, but MSSQL has its own specific implementation due to some extra differences in transact-SQL.

func (*BaseFlavor) GetEntitiesWithCommands

func (bf *BaseFlavor) GetEntitiesWithCommands(ents interface{}, pList []GetParam, cmdMap map[string]interface{}) (interface{}, error)

GetEntitiesWithCommands - it is recommended to use GetEntitiesCP instead of this method This method can be used as a get for lists of entities. Each DB needs slightly different handling due to differences in OFFSET / LIMIT / TOP support. This is a mostly common version, but MSSQL has its own specific implementation due to some extra differences in transact-SQL. This method still requires that the caller perform a type-assertion on the returned interface{} ([]interface{}) parameter.

func (*BaseFlavor) GetEntity

func (bf *BaseFlavor) GetEntity(ent interface{}) error

GetEntity - CRUD GetEntity gets an existing entity from the db using the primary key definition. It is expected that ID will have been populated in the body by the caller.

func (*BaseFlavor) GetNextSequenceValue

func (bf *BaseFlavor) GetNextSequenceValue(name string) (int, error)

GetNextSequenceValue returns the next value of the named or derived sequence, auto-increment or identity field depending on which db-system is presently being used.

func (*BaseFlavor) GetRelations

func (bf *BaseFlavor) GetRelations(tn string) []string

GetRelations is designed to take a tablename and use it to determine a list of related objects. this is just an idea, and the functionality will reqiure more than the return of a []string.

func (*BaseFlavor) IsDBLog

func (bf *BaseFlavor) IsDBLog() bool

IsDBLog reports whether db-access-logging is active

func (*BaseFlavor) IsLog

func (bf *BaseFlavor) IsLog() bool

IsLog reports whether logging is active

func (*BaseFlavor) Log

func (bf *BaseFlavor) Log(b bool)

Log sets the logging status

func (*BaseFlavor) ProcessSchema

func (bf *BaseFlavor) ProcessSchema(schema string)

ProcessSchema processes the schema against the connected DB.

func (*BaseFlavor) ProcessSchemaList

func (bf *BaseFlavor) ProcessSchemaList(sList []string)

ProcessSchemaList processes the schemas contained in sList in the order in which they were provided. Schemas are executed against the connected DB. DEPRECATED: USER ProcessTransactionList

func (*BaseFlavor) ProcessTransaction

func (bf *BaseFlavor) ProcessTransaction(tList []string) error

ProcessTransaction processes the list of commands as a transaction. If any of the commands encounter an error, the transaction will be cancelled via a Rollback and the error message will be returned to the caller. It is assumed that tList contains bound queryStrings.

func (*BaseFlavor) QsLog

func (bf *BaseFlavor) QsLog(queryString string, qParams ...interface{})

QsLog is used to log SQL statements to stdout. Statements are text approximations of what was sent to the database. For the most part they should be correct, but quoting around parameter values is rudimentary.

func (*BaseFlavor) Select

func (bf *BaseFlavor) Select(dst interface{}, queryString string, args ...interface{}) error

Select reads some rows into the dst interface. This calls sqlx.Select(...)

func (*BaseFlavor) SetDB

func (bf *BaseFlavor) SetDB(db *sqlx.DB)

SetDB sets the sqlx.DB connection in the db-flavor environment.

func (*BaseFlavor) SetMaxIdleConns

func (bf *BaseFlavor) SetMaxIdleConns(n int)

SetMaxIdleConns calls sqlx.SetMaxIdleConns

func (*BaseFlavor) SetMaxOpenConns

func (bf *BaseFlavor) SetMaxOpenConns(n int)

SetMaxOpenConns calls sqlx.SetMaxOpenConns

func (*BaseFlavor) TimeToFormattedString

func (bf *BaseFlavor) TimeToFormattedString(i interface{}) string

TimeToFormattedString is used to format the provided time.Time or *time.Time value in the string format required for the connected db insert or update operation. This method is called from within the CRUD ops for each db flavor, and could be added to the flavor-specific Query / Exec methods at some point.

type ColComponents

type ColComponents struct {
	// contains filtered or unexported fields
}

ColComponents is used to capture the field properties from sqac: tags during table creation and table alteration activities.

type CrudInfo

type CrudInfo struct {
	// contains filtered or unexported fields
}

CrudInfo contains information used to perform CRUD activities. Pre-call and post-call organization and formatting. v = Value (underlying struct of interface ptr ent)

type FKeyInfo

type FKeyInfo struct {
	FromTable string
	FromField string
	RefTable  string
	RefField  string
	FKeyName  string
}

FKeyInfo holds foreign-key defs read from the sqac:"fkey" tags sqac:"fkey:ref_table(ref_field)"

type ForeignKeyBuffer

type ForeignKeyBuffer struct {
	// contains filtered or unexported fields
}

ForeignKeyBuffer is used to hold deferred foreign-key information pending the creation of all tables submitted in a CreateTable(...) or AlterTable(...) call.

type GetEnt

type GetEnt interface {
	Exec(handle PublicDB) error
}

GetEnt interface is provided for retrieval of slices containing arbitrary structs. Exec accepts a handle to a PublicDB interface, thereby providing access to the DB sqac is connected to. it is up to the caller to code the underlying value / code. As awful as this seems, it eliminates the need to abuse reflection or unsafe pointers to achieve the goal of a general CRUD API.

type GetParam

type GetParam struct {
	FieldName    string
	Operand      string
	ParamValue   interface{}
	NextOperator string
}

GetParam defines a common structure for CRUD GET parameters.

type HDBFlavor

type HDBFlavor struct {
	BaseFlavor
}

HDBFlavor is a SAP Hana-specific implementation, where the Hana DB is approached as a traditional SQL-92 compliant database. As such, some of the nice HDB things are left out. Methods defined in the PublicDB interface of struct-type BaseFlavor are called by default for HDBFlavor. If the method as it exists in the BaseFlavor implementation is not compatible with the schema-syntax required by HDB, the method in question may be overridden. Overriding (redefining) a BaseFlavor method may be accomplished through the addition of a matching method signature and implementation on the HDBFlavor struct-type.

func (*HDBFlavor) AlterTables

func (hf *HDBFlavor) AlterTables(i ...interface{}) error

AlterTables alters tables on the HDB database referenced by hf.DB.

func (*HDBFlavor) Create

func (hf *HDBFlavor) Create(ent interface{}) error

Create the entity (single-row) on the database

func (*HDBFlavor) CreateSequence

func (hf *HDBFlavor) CreateSequence(sn string, start int)

CreateSequence is used to create a sequence for use with HDB Identity columns.

func (*HDBFlavor) CreateTables

func (hf *HDBFlavor) CreateTables(i ...interface{}) error

CreateTables creates tables on the hdb database referenced by hf.DB.

func (*HDBFlavor) DestructiveResetTables

func (hf *HDBFlavor) DestructiveResetTables(i ...interface{}) error

DestructiveResetTables drops tables on the HDB db if they exist, as well as any related objects such as sequences. this is useful if you wish to regenerated your table and the number-range used by an auto-incementing primary key.

func (*HDBFlavor) DropIndex

func (hf *HDBFlavor) DropIndex(tn string, in string) error

DropIndex drops the specfied index on the connected database.

func (*HDBFlavor) DropSequence

func (hf *HDBFlavor) DropSequence(sn string) error

DropSequence is used to drop an existing sequence in HDB.

func (*HDBFlavor) DropTables

func (hf *HDBFlavor) DropTables(i ...interface{}) error

DropTables drops tables on the db if they exist, based on the provided list of go struct definitions.

func (*HDBFlavor) ExistsColumn

func (hf *HDBFlavor) ExistsColumn(tn string, cn string) bool

ExistsColumn checks the currently connected database and returns true if the named table-column is found to exist. this checks the column name only, not the column data-type or properties.

func (*HDBFlavor) ExistsForeignKeyByFields

func (hf *HDBFlavor) ExistsForeignKeyByFields(i interface{}, ft, rt, ff, rf string) (bool, error)

ExistsForeignKeyByFields checks to see if a foreign-key exists between the named tables and fields.

func (*HDBFlavor) ExistsForeignKeyByName

func (hf *HDBFlavor) ExistsForeignKeyByName(i interface{}, fkn string) (bool, error)

ExistsForeignKeyByName checks to see if the named foreign-key exists on the table corresponding to provided sqac model (i).

func (*HDBFlavor) ExistsIndex

func (hf *HDBFlavor) ExistsIndex(tn string, in string) bool

ExistsIndex checks the connected database for the presence of the specified index.

func (*HDBFlavor) ExistsSequence

func (hf *HDBFlavor) ExistsSequence(sn string) bool

ExistsSequence is used to check for the existence of the named sequence in HDB.

func (*HDBFlavor) ExistsTable

func (hf *HDBFlavor) ExistsTable(tn string) bool

ExistsTable checks the currently connected database and returns true if the named table is found to exist.

func (*HDBFlavor) GetDBName

func (hf *HDBFlavor) GetDBName() (dbName string)

GetDBName returns the first db in the list - should only be one?

func (*HDBFlavor) GetNextSequenceValue

func (hf *HDBFlavor) GetNextSequenceValue(name string) (int, error)

GetNextSequenceValue is used primarily for testing. It returns the next value of the named HDB identity (auto-increment) field in the named table. this is not a reliable way to get the inserted id in a multi-transaction environment.

func (*HDBFlavor) Update

func (hf *HDBFlavor) Update(ent interface{}) error

Update an existing entity (single-row) on the database

type IndexInfo

type IndexInfo struct {
	TableName   string
	Unique      bool
	IndexFields []string
}

IndexInfo contains index definitions as read from the sqac:"index" tags

type MSSQLFlavor

type MSSQLFlavor struct {
	BaseFlavor
}

MSSQLFlavor is a MSSQL-specific implementation. Methods defined in the PublicDB interface of struct-type BaseFlavor are called by default for MSSQLFlavor. If the method as it exists in the BaseFlavor implementation is not compatible with the schema-syntax required by MSSQL, the method in question may be overridden. Overriding (redefining) a BaseFlavor method may be accomplished through the addition of a matching method signature and implementation on the MSSQLFlavor struct-type.

func (*MSSQLFlavor) AlterSequenceStart

func (msf *MSSQLFlavor) AlterSequenceStart(name string, start int) error

AlterSequenceStart may be used to make changes to the start value of the named identity-field on the currently connected MSSQL database.

func (*MSSQLFlavor) AlterTables

func (msf *MSSQLFlavor) AlterTables(i ...interface{}) error

AlterTables alters tables on the MSSQL database referenced by msf.DB.

func (*MSSQLFlavor) Create

func (msf *MSSQLFlavor) Create(ent interface{}) error

Create the entity (single-row) on the database

func (*MSSQLFlavor) CreateTables

func (msf *MSSQLFlavor) CreateTables(i ...interface{}) error

CreateTables creates tables on the mysql database referenced by msf.DB.

func (*MSSQLFlavor) DestructiveResetTables

func (msf *MSSQLFlavor) DestructiveResetTables(i ...interface{}) error

DestructiveResetTables drops tables on the MSSQL db if they exist, as well as any related objects such as sequences. this is useful if you wish to regenerated your table and the number-range used by an auto-incementing primary key.

func (*MSSQLFlavor) DropIndex

func (msf *MSSQLFlavor) DropIndex(tn string, in string) error

DropIndex drops the specfied index on the connected database.

func (*MSSQLFlavor) DropTables

func (msf *MSSQLFlavor) DropTables(i ...interface{}) error

DropTables drops tables on the db if they exist, based on the provided list of go struct definitions.

func (*MSSQLFlavor) ExistsColumn

func (msf *MSSQLFlavor) ExistsColumn(tn string, cn string) bool

ExistsColumn checks the currently connected database and returns true if the named table-column is found to exist. this checks the column name only, not the column data-type or properties.

func (*MSSQLFlavor) ExistsForeignKeyByFields

func (msf *MSSQLFlavor) ExistsForeignKeyByFields(i interface{}, ft, rt, ff, rf string) (bool, error)

ExistsForeignKeyByFields checks to see if a foreign-key exists between the named tables and fields.

func (*MSSQLFlavor) ExistsForeignKeyByName

func (msf *MSSQLFlavor) ExistsForeignKeyByName(i interface{}, fkn string) (bool, error)

ExistsForeignKeyByName checks to see if the named foreign-key exists on the table corresponding to provided sqac model (i).

func (*MSSQLFlavor) ExistsIndex

func (msf *MSSQLFlavor) ExistsIndex(tn string, in string) bool

ExistsIndex checks the connected database for the presence of the specified index.

func (*MSSQLFlavor) ExistsTable

func (msf *MSSQLFlavor) ExistsTable(tn string) bool

ExistsTable checks the currently connected database and returns true if the named table is found to exist.

func (*MSSQLFlavor) GetDBName

func (msf *MSSQLFlavor) GetDBName() (dbName string)

GetDBName returns the name of the currently connected db

func (*MSSQLFlavor) GetEntitiesCP

func (msf *MSSQLFlavor) GetEntitiesCP(ents interface{}, pList []GetParam, cmdMap map[string]interface{}) (result uint64, err error)

GetEntitiesCP is a parameterized get. See the BaseFlavor implementation for more info.

func (*MSSQLFlavor) GetEntitiesWithCommands

func (msf *MSSQLFlavor) GetEntitiesWithCommands(ents interface{}, pList []GetParam, cmdMap map[string]interface{}) (interface{}, error)

GetEntitiesWithCommands is a parameterized get. See the BaseFlavor implementation for more info.

func (*MSSQLFlavor) GetNextSequenceValue

func (msf *MSSQLFlavor) GetNextSequenceValue(name string) (int, error)

GetNextSequenceValue is used primarily for testing. It returns the current value of the MSSQL identity (auto-increment) field for the named table.

func (*MSSQLFlavor) Update

func (msf *MSSQLFlavor) Update(ent interface{}) error

Update an existing entity (single-row) on the database

type MySQLFlavor

type MySQLFlavor struct {
	BaseFlavor
}

MySQLFlavor is a MySQL-specific implementation. Methods defined in the PublicDB interface of struct-type BaseFlavor are called by default for MySQLFlavor. If the method as it exists in the BaseFlavor implementation is not compatible with the schema-syntax required by MySQL, the method in question may be overridden. Overriding (redefining) a BaseFlavor method may be accomplished through the addition of a matching method signature and implementation on the MySQLFlavor struct-type.

func (*MySQLFlavor) AlterSequenceStart

func (myf *MySQLFlavor) AlterSequenceStart(name string, start int) error

AlterSequenceStart may be used to make changes to the start value of the named auto_increment field in the MySQL database. Note that this is intended to deal with auto-incrementing primary keys only. It is possible in MySQL to setup a non-primary-key field as auto_increment as follows:

 ALTER TABLE users ADD id INT UNSIGNED NOT NULL AUTO_INCREMENT, ADD INDEX (id);

This is not presently supported.

func (*MySQLFlavor) AlterTables

func (myf *MySQLFlavor) AlterTables(i ...interface{}) error

AlterTables alters tables on the MySQL database referenced by myf.DB.

func (*MySQLFlavor) Create

func (myf *MySQLFlavor) Create(ent interface{}) error

Create the entity (single-row) on the database

func (*MySQLFlavor) CreateTables

func (myf *MySQLFlavor) CreateTables(i ...interface{}) error

CreateTables creates tables on the mysql database referenced by myf.DB.

func (*MySQLFlavor) DestructiveResetTables

func (myf *MySQLFlavor) DestructiveResetTables(i ...interface{}) error

DestructiveResetTables drops tables on the MySQL db if they exist, as well as any related objects such as sequences. this is useful if you wish to regenerated your table and the number-range used by an auto-incementing primary key.

func (*MySQLFlavor) DropForeignKey

func (myf *MySQLFlavor) DropForeignKey(i interface{}, ft, fkn string) error

DropForeignKey drops a foreign-key on an existing column

func (*MySQLFlavor) DropIndex

func (myf *MySQLFlavor) DropIndex(tn string, in string) error

DropIndex drops the specfied index on the connected database.

func (*MySQLFlavor) ExistsForeignKeyByFields

func (myf *MySQLFlavor) ExistsForeignKeyByFields(i interface{}, ft, rt, ff, rf string) (bool, error)

ExistsForeignKeyByFields checks to see if a foreign-key exists between the named tables and fields.

func (*MySQLFlavor) ExistsForeignKeyByName

func (myf *MySQLFlavor) ExistsForeignKeyByName(i interface{}, fkn string) (bool, error)

ExistsForeignKeyByName checks to see if the named foreign-key exists on the table corresponding to provided sqac model (i).

func (*MySQLFlavor) GetNextSequenceValue

func (myf *MySQLFlavor) GetNextSequenceValue(name string) (int, error)

GetNextSequenceValue is used primarily for testing. It returns the current value of the MySQL auto-increment field for the named table.

func (*MySQLFlavor) Update

func (myf *MySQLFlavor) Update(ent interface{}) error

Update an existing entity (single-row) on the database

type PostgresFlavor

type PostgresFlavor struct {
	BaseFlavor
}

PostgresFlavor is a postgres-specific implementation. Methods defined in the PublicDB interface of struct-type BaseFlavor are called by default for PostgresFlavor. If the method as it exists in the BaseFlavor implementation is not compatible with the schema-syntax required by Postgres, the method in question may be overridden. Overriding (redefining) a BaseFlavor method may be accomplished through the addition of a matching method signature and implementation on the PostgresFlavor struct-type.

func (*PostgresFlavor) AlterSequenceStart

func (pf *PostgresFlavor) AlterSequenceStart(sn string, start int) error

AlterSequenceStart adjusts the starting value of the named sequence. This should be called very carefully, preferably only at the time that the table/sequence is created on the db. There are no safeguards here.

func (*PostgresFlavor) AlterTables

func (pf *PostgresFlavor) AlterTables(i ...interface{}) error

AlterTables alters tables on the Postgres database referenced by pf.DB.

func (*PostgresFlavor) Create

func (pf *PostgresFlavor) Create(ent interface{}) error

Create the entity (single-row) on the database

func (*PostgresFlavor) CreateSequence

func (pf *PostgresFlavor) CreateSequence(sn string, start int)

CreateSequence creates the required sequence on the connected Postgres database in the public schema. Panics on error.

func (*PostgresFlavor) CreateTables

func (pf *PostgresFlavor) CreateTables(i ...interface{}) error

CreateTables creates tables on the postgres database referenced by pf.DB.

func (*PostgresFlavor) DestructiveResetTables

func (pf *PostgresFlavor) DestructiveResetTables(i ...interface{}) error

DestructiveResetTables drops tables on the db if they exist, as well as any related objects such as sequences. this is useful if you wish to regenerated your table and the number-range used by an auto-incementing primary key.

func (*PostgresFlavor) DropIndex

func (pf *PostgresFlavor) DropIndex(tn string, in string) error

DropIndex drops the specfied index on the connected Postgres database. tn is ignored for Postgres.

func (*PostgresFlavor) DropTables

func (pf *PostgresFlavor) DropTables(i ...interface{}) error

DropTables drops tables on the postgres database referenced by pf.DB.

func (*PostgresFlavor) ExistsColumn

func (pf *PostgresFlavor) ExistsColumn(tn string, cn string) bool

ExistsColumn checks for the existence of the specified table-column checking for the column name. this is rather incomplete, but in many cases where there is a type-discrepancy it is necessary to drop and recreate the column - or the entire table if a key is involved. consider also that pg requies autoincrement fields to be specified as 'serial' or 'bigserial', but then goes on to report them as 'integer' in the actual db-scema.

func (*PostgresFlavor) ExistsForeignKeyByFields

func (pf *PostgresFlavor) ExistsForeignKeyByFields(i interface{}, ft, rt, ff, rf string) (bool, error)

ExistsForeignKeyByFields checks to see if a foreign-key exists between the named tables and fields.

func (*PostgresFlavor) ExistsForeignKeyByName

func (pf *PostgresFlavor) ExistsForeignKeyByName(i interface{}, fkn string) (bool, error)

ExistsForeignKeyByName checks to see if the named foreign-key exists on the table corresponding to provided sqac model (i).

func (*PostgresFlavor) ExistsIndex

func (pf *PostgresFlavor) ExistsIndex(tn string, in string) bool

ExistsIndex checks the connected Postgres database for the presence of the specified index - assuming that the index-type has not been adjusted...

func (*PostgresFlavor) ExistsSequence

func (pf *PostgresFlavor) ExistsSequence(sn string) bool

ExistsSequence checks the public schema of the connected Postgres DB for the existence of the provided sequence name.

func (*PostgresFlavor) ExistsTable

func (pf *PostgresFlavor) ExistsTable(tn string) bool

ExistsTable checks the public schema of the connected Postgres DB for the existence of the provided table name. Note that the use of to_regclass(<obj_name>) checks for the existence of *any* object in the public schema that has that name. If obj/name consistency is maintained, this approach is fine.

func (*PostgresFlavor) GetDBName

func (pf *PostgresFlavor) GetDBName() (dbName string)

GetDBName returns the name of the currently connected db

func (*PostgresFlavor) GetNextSequenceValue

func (pf *PostgresFlavor) GetNextSequenceValue(name string) (int, error)

GetNextSequenceValue is used primarily for testing. It returns the current value of the sequence assigned to the primary-key of the of the named Postgres table. Although it is possible to assign Postgres sequences to non-primary-key fields (composite key gen), sqac handle auto-increment as a primary-key constraint only.

func (*PostgresFlavor) Update

func (pf *PostgresFlavor) Update(ent interface{}) error

Update an existing entity (single-row) on the database

type PublicDB

type PublicDB interface {

	// postgres, sqlite, mariadb, hdb, hana etc.
	GetDBDriverName() string

	// activate / check logging
	Log(b bool)
	IsLog() bool
	DBLog(b bool)
	IsDBLog() bool

	// set the *sqlx.DB handle for the PublicDB interface
	SetDB(db *sqlx.DB)
	GetDB() *sqlx.DB

	// GetDBName reports the name of the currently connected db for
	// information_schema access.  File-based databases like
	// sqlite report the name as the absolute path to the location
	// of their database file.
	GetDBName() string

	// GetDBQuote reports the quoting preference for db-query construction.
	// ' vs ` vs " for example
	GetDBQuote() string

	// Close the db-connection
	Close() error

	// set / get the max idle sqlx db-connections and max open sqlx db-connections
	SetMaxIdleConns(n int)
	SetMaxOpenConns(n int)

	GetRelations(tn string) []string

	// i=db/sqac tagged go struct-type
	CreateTables(i ...interface{}) error
	DropTables(i ...interface{}) error
	AlterTables(i ...interface{}) error
	DestructiveResetTables(i ...interface{}) error
	ExistsTable(tn string) bool

	// tn=tableName, cn=columnName
	ExistsColumn(tn string, cn string) bool

	// tn=tableName, in=indexName
	CreateIndex(in string, index IndexInfo) error
	DropIndex(tn string, in string) error
	ExistsIndex(tn string, in string) bool

	// sn=sequenceName, start=start-value, name is used to hold
	// the name of the sequence, autoincrement or identity
	// field name.  the use of name depends on which db system
	// has been connected.
	CreateSequence(sn string, start int)
	AlterSequenceStart(name string, start int) error
	GetNextSequenceValue(name string) (int, error)
	// select pg_get_serial_sequence('public.some_table', 'some_column');
	DropSequence(sn string) error
	ExistsSequence(sn string) bool

	// CreateForeignKey(Entity{}, foreignkeytable, reftable, fkfield, reffield)
	// &Entity{} (i) is only needed for SQLite - okay to pass nil in other cases.
	CreateForeignKey(i interface{}, ft, rt, ff, rf string) error
	DropForeignKey(i interface{}, ft, fkn string) error
	ExistsForeignKeyByName(i interface{}, fkn string) (bool, error)
	ExistsForeignKeyByFields(i interface{}, ft, rt, ff, rf string) (bool, error)

	// process DDL/DML commands
	ProcessSchema(schema string)
	ProcessSchemaList(sList []string)
	ProcessTransaction(tList []string) error

	// sql package access
	ExecuteQueryRow(queryString string, qParams ...interface{}) *sql.Row
	ExecuteQuery(queryString string, qParams ...interface{}) (*sql.Rows, error)
	Exec(queryString string, args ...interface{}) (sql.Result, error)

	// sqlx package access
	ExecuteQueryRowx(queryString string, qParams ...interface{}) *sqlx.Row
	ExecuteQueryx(queryString string, qParams ...interface{}) (*sqlx.Rows, error)
	Get(dst interface{}, queryString string, args ...interface{}) error
	Select(dst interface{}, queryString string, args ...interface{}) error

	// Boolean conversions
	BoolToDBBool(b bool) *int
	DBBoolToBool(interface{}) bool
	TimeToFormattedString(i interface{}) string

	// CRUD ops
	Create(ent interface{}) error
	Update(ent interface{}) error
	Delete(ent interface{}) error    // (id uint) error
	GetEntity(ent interface{}) error // pass ptr to type containing key information
	GetEntities(ents interface{}) (interface{}, error)
	GetEntities2(ge GetEnt) error
	GetEntities4(ents interface{})
	GetEntitiesCP(ents interface{}, pList []GetParam, cmdMap map[string]interface{}) (uint64, error)
	GetEntitiesWithCommands(ents interface{}, pList []GetParam, cmdMap map[string]interface{}) (interface{}, error)
}

PublicDB exposes functions for db related operations.

func Create

func Create(flavor string, logFlag bool, dbLogFlag bool, connectionString string) (handle PublicDB)

Create establishes a connection with the db based on the connectionString. A handle conforming to the sqac.PublicDB interface is passed back to the caller. The type of the underlying handle object is that of the DBFlavor corresponding to the flavor var in the function definition.

type SQLiteFlavor

type SQLiteFlavor struct {
	BaseFlavor
}

SQLiteFlavor is a sqlite3-specific implementation. Methods defined in the PublicDB interface of struct-type BaseFlavor are called by default for SQLiteFlavor. If the method as it exists in the BaseFlavor implementation is not compatible with the schema-syntax required by SQLite, the method in question may be overridden. Overriding (redefining) a BaseFlavor method may be accomplished through the addition of a matching method signature and implementation on the SQLiteFlavor struct-type.

func (*SQLiteFlavor) AlterSequenceStart

func (slf *SQLiteFlavor) AlterSequenceStart(name string, start int) error

AlterSequenceStart may be used to make changes to the start value of the auto-increment field on the currently connected SQLite database file. This method is intended to be called at the time of table-creation, as updating the current value of the SQLite auto-increment may cause unanticipated difficulties if the target table already contains records.

func (*SQLiteFlavor) AlterTables

func (slf *SQLiteFlavor) AlterTables(i ...interface{}) error

AlterTables alters tables on the SQLite database referenced by slf.DB.

func (*SQLiteFlavor) Create

func (slf *SQLiteFlavor) Create(ent interface{}) error

Create the entity (single-row) on the database

func (*SQLiteFlavor) CreateForeignKey

func (slf *SQLiteFlavor) CreateForeignKey(i interface{}, ft, rt, ff, rf string) error

CreateForeignKey creates a foreign key on an existing column in the database table specified by the i / ft parameter. SQLite does not support the addition of a foreign-key via ALTER TABLE, so the existing table has to be copied to a backup, and a new table created (hence parameter i) with the foreign-key constraint in the CREATE TABLE ... command. Foreign-key constraints are temporarily disabled on the db for the duration of the transaction processing. THIS SHOULD NOT BE CALLED DIRECTLY. IT IS FAR SAFER IN THE SQLITE CASE TO UPDATE THE SQAC-TAGS ON THE TABLE'S MODEL.

func (*SQLiteFlavor) CreateTables

func (slf *SQLiteFlavor) CreateTables(i ...interface{}) error

CreateTables creates tables on the sqlite3 database referenced by slf.DB.

func (*SQLiteFlavor) DestructiveResetTables

func (slf *SQLiteFlavor) DestructiveResetTables(i ...interface{}) error

DestructiveResetTables drops tables on the SQLite db file if they exist, as well as any related objects such as sequences. this is useful if you wish to regenerated your table and the number-range used by an auto-incementing primary key.

func (*SQLiteFlavor) DropForeignKey

func (slf *SQLiteFlavor) DropForeignKey(i interface{}, ft, fkn string) error

DropForeignKey drops a foreign-key on an existing column. Since SQLite does not support the addition or deletion of foreign-key relationships on existing tables, the existing table is copied to a backup table, dropped and then recreated using the sqac model information contained in (i). It follows that in order for a foreign-key to be dropped, it must be removed from the sqac tag in the model definition.

func (*SQLiteFlavor) DropIndex

func (slf *SQLiteFlavor) DropIndex(tn string, in string) error

DropIndex drops the specfied index on the connected SQLite database. SQLite does not require the table name to drop an index, but it is provided in order to comply with the PublicDB interface definition.

func (*SQLiteFlavor) DropTables

func (slf *SQLiteFlavor) DropTables(i ...interface{}) error

DropTables drops tables on the SQLite db if they exist, based on the provided list of go struct definitions.

func (*SQLiteFlavor) ExistsColumn

func (slf *SQLiteFlavor) ExistsColumn(tn string, cn string) bool

ExistsColumn checks the current SQLite database file and returns true if the named table-column is found to exist. this checks the column name only, not the column data-type or properties.

func (*SQLiteFlavor) ExistsForeignKeyByFields

func (slf *SQLiteFlavor) ExistsForeignKeyByFields(i interface{}, ft, rt, ff, rf string) (bool, error)

ExistsForeignKeyByFields checks to see if a foreign-key exists between the named tables and fields.

func (*SQLiteFlavor) ExistsForeignKeyByName

func (slf *SQLiteFlavor) ExistsForeignKeyByName(i interface{}, fkn string) (bool, error)

ExistsForeignKeyByName checks to see if the named foreign-key exists on the table corresponding to provided sqac model (i).

func (*SQLiteFlavor) ExistsIndex

func (slf *SQLiteFlavor) ExistsIndex(tn string, in string) bool

ExistsIndex checks the connected SQLite database for the presence of the specified index. This method is typically not required for SQLite, as the 'IF EXISTS' syntax is widely supported.

func (*SQLiteFlavor) ExistsTable

func (slf *SQLiteFlavor) ExistsTable(tn string) bool

ExistsTable checks that the specified table exists in the SQLite database file.

func (*SQLiteFlavor) GetDBName

func (slf *SQLiteFlavor) GetDBName() (dbName string)

GetDBName returns the name of the currently connected db

func (*SQLiteFlavor) GetNextSequenceValue

func (slf *SQLiteFlavor) GetNextSequenceValue(name string) (int, error)

GetNextSequenceValue is used primarily for testing. It returns the current value of the SQLite auto-increment field for the named table.

func (*SQLiteFlavor) Update

func (slf *SQLiteFlavor) Update(ent interface{}) error

Update an existing entity (single-row) on the database

type Sqac

type Sqac struct {
	Hndl PublicDB
}

Sqac is the main access structure for the sqac library.

type TblComponents

type TblComponents struct {
	// contains filtered or unexported fields
}

TblComponents is used as a collector structure for internal table create / alter processing.

func (*TblComponents) Log

func (tc *TblComponents) Log()

Log dumps all of the raw table components to stdout is called for CreateTable and AlterTable operations if the main sqac logging has been activated via BaseFlavor.Log(true).

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL