enjinql

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2024 License: Apache-2.0 Imports: 27 Imported by: 8

README

godoc codecov Go Report Card

enjinql - Enjin Query Language

enjinql is the reference implementation of the Enjin Query Language, a feature of Go-Enjin that manages the indexing and accessing of page content.

Notice

While this project is in active use within the Go-Enjin project and example sites, enjinql does not have a sufficient amount of unit tests and the syntax has not been ratified as a formal specification.

Please consider enjinql as a proof-of-concept that is already on its way to being a minimum viable product and for the time being, probably not the best choice to use in non-Go-Enjin projects.

Installation

> go get github.com/go-corelibs/enjinql@latest

Command Line Interface

> go install github.com/go-corelibs/enjinql/cmd/enjinql@latest

Go-CoreLibs

Go-CoreLibs is a repository of shared code between the Go-Curses and Go-Enjin projects.

License

Copyright 2024 The Go-CoreLibs Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use file except in compliance with the License.
You may obtain a copy of the license at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Documentation

Overview

Package enjinql provides an abstracted system for indexing and querying a collection of content.

Sources is an interface for managing and querying a collection of Source instances.

Source is an interface for managing the correlation of contextual information with the collection of content.

Source Forms

There are currently three forms of what a Source is, conceptually speaking.

The first is the "primary source" that the Enjin Query Language is tasked with indexing. This is not the actual data being stored in a table per-se, just the necessary bits for providing a reference back to the actual content (like most other indexing systems). For example, in the use of the fantastic bleve search system, the site developer must provide a mapping of fields to index and then for each url of content, pass the url and its specific fields to bleve to index. bleve analyses the individual fields of content and using its internal primary source (likely a btree or some other wizardry), links this listing of URLs to the content fields provided. Once the site indexing process is complete, the developer can easily produce a search page which end-users can use the bleve search syntax to find content present in the site.

For the Enjin Query Language, more than just the URL is important. There is still the URL of course but there is a greater context present as well. There is the page's shasum identifier, the page's type of content, language and so on to form a contextual index for finding content. This isn't to replace bleve within Go-Enjin; end-user search features are notoriously difficult to get right and bleve excels at getting this right. As a developer of a Go-Enjin based system, writing features often requires querying the enjin for a contextual case that may not involve the URL at all and to be forced to query for a URL in order to query for a page's specific context becomes a rather tedious repetition of code that isn't easily abstracted. Cases like that are examples of what could be called the farming of technical debt. Of course developers don't like farming technical debt and because one of the main purposes of the Go-Enjin project is to cater to the needs of the developer rather than just the employers and end-users, Go-Enjin necessitates a system for managing and querying a context of information.

The second form of Source is the "data source". These are sources of data that relate to the primary source but do not actually have any direct correlation with the primary source context. For example, if the developer needs to build up a list of distinct words found within any given page of content, it is beneficial to have an index of all distinct words in general and to then use another form of source which joins the data source with the primary source.

The final form of Source is the "link source" and as noted in the data source description, these sources typically have only unique identifiers which link two data sources together. This is the basis for how the Enjin Query Language can construct SQL queries with multiple JOIN statements without the developer having to spell out the table relationships in their use of the Enjin Query Language.

Real World Example

One of the Go-Enjin website projects is an experiment in exploring human thought through the strange indexing of a large set of quotes from various sources. This website is called Quoted.FYI and in specific, it allows visitors to select from a list of "first words" from all quotes and then select from another list of "next words", and so on, building up to a narrow selection of quotes sharing the same series of opening words. To implement this feature, systems like bleve just aren't designed for it. Let's take a little walkthrough of the Quoted.FYI website project.

## Quotes

  • a quote is just a normal Go-Enjin page with a custom type of "quote"
  • each quote has a context consisting of an author, a list of topics, the body of the quote and a SHA1 hashing of the content (used to help avoid exact duplicates though "plural specificity" is allowed)

## Statistics

  • 482,673 quotes
  • 23,731 authors
  • 11,980 topics
  • 98,879 words

## EnjinQL Setup

Quoted.FYI of course uses the default Go-Enjin primary source which consists of a number of specific columns of information related to any given page.

| (Page Primary Source)                                  |
| Key      | Description                                 |
----------------------------------------------------------
| id       | an integer primary key                      |
| shasum   | specific version of a specific page         |
| language | language code of a specific page            |
| url      | relative URL path to a specific page        |
| stub     | JSON blob used to construct a specific page |

These are the commonly used context keys when implementing Go-Enjin features, and so they're all a part of the primary enjinql source.

For each of the authors, topics and words information, Quoted.FYI needs additional indexing to support these various things. They're relatively the same but let's take a look at the words indexing in more detail.

| (Word Data Source)                                          |
| Key     | Description                                       |
---------------------------------------------------------------
| id      | an integer primary key                            |
| word    | lower-cased plain text, unique, word              |
| flat    | a snake_cased version of word used in legacy URLs |

The above data source, describes the list of distinct words used to consolidate the space required by the underlying database.

So, now there are two sources defined, the primary one and the list of unique words. These two sources, being entirely unrelated to each other would result in a query error if used together.

For example, to look up a list of page shasums for the "en" language:

(EQL) LOOKUP .Shasum WITHIN .Language == "en"
(SQL) SELECT be_eql_page.shasum
      FROM be_eql_page
      WHERE be_eql_page.language = "en";

The above query would work and return the list of all page shasums that are of the "en" language.

(EQL) LOOKUP word.Word WITHIN .Word ^= "a"
(SQL) SELECT be_eql_word.word
      FROM be_eql_word
      WHERE be_eql_word LIKE "a%";

The above query again would work, this time returning a list of all words starting with the letter "a". While not the most efficient due to the use of the "starts with" (^=) operator, it does perform well.

Given just the primary and word data sources, the following query would not work (yet):

(EQL) LOOKUP .Shasum WITHIN word.Word == "thing"
(SQL) error: "be_eql_page" is not linked with "be_eql_word"

The above query is supposed to return a list of page shasums where the page uses the word "thing" at least once. To make this work, the needs to be a link source connecting the relationship of pages to words within each page.

| (Page Words Link Source)                                  |
| Key     | Description                                     |
-------------------------------------------------------------
| id      | an integer primary key                          |
| page_id | id of the primary source                        |
| word_id | id of the word data source                      |
| hits    | how many times the word is used within the page |

The above link source joins the primary source with the word data source and includes an extra count of how many times that specific word is used within the specific quote.

Now, the query to get the list of pages with the word "thing":

(EQL) LOOKUP .Shasum WITHIN word.Word == "thing"
(SQL) SELECT be_eql_page.shasum
      FROM be_eql_page
      INNER JOIN be_eql_page_words ON be_eql_page.id=be_eql_page_words.page_id
      INNER JOIN be_eql_words      ON be_eql_word.id=be_eql_page_words.word_id
      WHERE be_eql_word.word == "thing";

This demonstrates the simplicity of the Enjin Query Language in that the EQL statements don't need to do an SQL magic directly, such as sorting out the table joins. This is all done by the developer simply defining the various sources and then populating them with the content available.

The below code demonstrates how to create the primary and word data sources depicted above:

// this is the top-level interface for interacting with the enjinql module
sources := enjinql.NewSources("be_eql")
// build the primary source
bePage, err := sources.newSource("page").
  StringValue("shasum", 10).               // shasum primary value (10 bytes)
  AddStringValue("language", 7).           // page language code (7 bytes)
  AddStringValue("type", 32).              // custom page type (32 bytes)
  AddStringValue("url", 2000).             // page url (2000 bytes)
  AddStringValue("stub", -1).              // enjin stub (default TEXT size)
  AddUnique("shasum", "language", "url").  // add a UNIQUE constraint on shasum + language + url
  AddIndex("shasum").                      // add a specific shasum sql index
  AddIndex("language").                    // add a specific language sql index
  AddIndex("type").                        // add a specific type sql index
  AddIndex("url").                         // add a specific url sql index
  Make()                                   // make the source instance
// build the word data source
beWord, err := sources.newSource("word").
  StringValue("word", 256).                // word primary value (256 bytes)
  AddStringValue("flat", 256).             // flat word value (256 bytes)
  AddUnique("word").                       // add a UNIQUE constraint on word
  AddIndex("word").                        // add a specific word sql index
  AddIndex("flat").                        // add a specific flat sql index
  Make()                                   // make the source instance
// build the page-word link source
bePageWords, err := bePage.newSource("words").
  LinkedValue("page", "id").               // page_id link to bePage source, id column
  AddLinkedValue("word", "id").            // word_id link to "word" source, id column
  AddIntValue("hits").                     // additional integer value
  AddUnique("page_id", "word_id").         // add a UNIQUE constraint on page_id + word_id
  AddIndex("page_id").                     // add a specific page_id index
  AddIndex("word_id").                     // add a specific word_id index
  Make()                                   // make the source instance

With the above constructed, the developer can now proceed with updating the sources instance with the content available.

// add a new page to the primary source
sid, err = bePage.Insert("1234567890", "en", "quote", "/q/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "{...json...}")
// for each of the distinct words present in the quote body, count the number of times the word is used, flatten
// the word and add it all to the word source
wid, err = beWord.InsertOrIgnore("word", "i'm", "i_m")
_, err = bePageWord.Insert(sid, wid, count)

That process needs to be expanded up of course for the complete site, but for just the primary, data and link sources defined so far, the enjinql instance is now ready to build the actual SQL from parsing EQL statements:

// get a list of shasums for all pages with the word "thing"
sql, argv, err = sources.Parse(`LOOKUP .Shasum WITHIN word.Word == "thing"`)
// sql  => "SELECT ... WHERE be_eql_word.word=?;"
// argv => []interface{"thing"}
// err  => nil

Code generated by Participle. DO NOT EDIT.

Index

Constants

View Source
const (
	PageSource       = "page"
	PageSourceIdKey  = "page_id"
	PageShasumKey    = "shasum"
	PageLanguageKey  = "language"
	PageTypeKey      = "type"
	PageArchetypeKey = "archetype"
	PageCreatedKey   = "created"
	PageUpdatedKey   = "updated"
	PageUrlKey       = "url"
	PageStubKey      = "stub"
)
View Source
const (
	// MaxUrlPathSize is the recommended 2000-character limit on overall URL
	// size, minus 256 for the domain segment and minus another nine for the
	// https://
	MaxUrlPathSize = 2000 - 256 - 8

	// MaxPageTypeSize is the Go-Enjin recommended 64-character limit on the
	// total length of custom page type and archetype names
	MaxPageTypeSize = 64

	// PageShasumSize is the length of a Go-Enjin page "shasum" identifier,
	// which is the first 10-characters of the SHA-256 hash of the complete
	// page content (front-matter plus body)
	PageShasumSize = 10
)
View Source
const (
	PagePermalinkSource   = "permalink"
	PagePermalinkShortKey = "short"
	PagePermalinkLongKey  = "long"
	ShortPermalinkSize    = 10 // size of [shasum.BriefLength]
	LongPermalinkSize     = 36 // length of hex-string uuid.V4 ((uuid.Size * 2) + 4)
)
View Source
const (
	PageRedirectSource = "redirect"
	PageRedirectKey    = "url"
)
View Source
const (
	// SourceIdKey is the SQL name of the primary key for all SQL tables
	SourceIdKey = "id"
)

Variables

View Source
var (
	ErrBuilderError    = errors.New("builder error")
	ErrInvalidSyntax   = errors.New("invalid syntax")
	ErrSyntaxValueType = errors.New("unsupported syntax value type")

	ErrNilStructure = errors.New("nil structure")

	ErrMismatchQuery  = errors.New("QUERY does not return keyed values; use LOOKUP for context specifics")
	ErrMismatchLookup = errors.New("LOOKUP does not return entire pages; use QUERY for complete pages")

	ErrNegativeOffset = errors.New("negative offset")
	ErrNegativeLimit  = errors.New("negative limit")

	ErrMissingSourceKey = errors.New("missing source key")
	ErrMissingOperator  = errors.New("missing operator")
	ErrMissingLeftSide  = errors.New("missing left-hand side expression")
	ErrMissingRightSide = errors.New("missing right-hand side expression")

	ErrInvalidConstraint = errors.New("invalid constraint")

	ErrInvalidInOp = errors.New("<SourceKey> [NOT] IN (<list>...)")

	ErrOpStringRequired = errors.New("operator requires a string argument")

	ErrTableNotFound  = errors.New("table not found")
	ErrColumnNotFound = errors.New("column not found")

	ErrInvalidJSON          = errors.New("invalid json data")
	ErrInvalidConfig        = errors.New("invalid config")
	ErrNoSources            = errors.New("at least one source is required")
	ErrNoSourceValues       = errors.New("at least one source value is required")
	ErrParentNotFound       = errors.New("parent not found")
	ErrNotSnakeCased        = errors.New("all names and keys must be snake_cased")
	ErrUnnamedSource        = errors.New("unnamed source")
	ErrEmptySourceValue     = errors.New("empty source value")
	ErrEmptySourceValueKey  = errors.New("source value key is empty")
	ErrSourceNotFound       = errors.New("source not found")
	ErrColumnConfigNotFound = errors.New("column config not found")
	ErrCreateIndexSQL       = errors.New("error building create index sql")
	ErrCreateIndex          = errors.New("error creating index sql")
	ErrCreateTableSQL       = errors.New("error building create table sql")
	ErrCreateTable          = errors.New("error creating table sql")

	ErrQueryRequiresStub = errors.New("eql query statements require a \"stub\" column")

	ErrDeleteRows    = errors.New("delete rows error")
	ErrInsertRow     = errors.New("insert row error")
	ErrTooManyValues = errors.New("too many values given")
	ErrNoValues      = errors.New("at least the first column value is required")
	ErrInvalidID     = errors.New("row identifiers must be greater than zero")

	ErrUnmarshalEnjinQL = errors.New("use enjinql.ParseConfig and enjinql.New to restore an EnjinQL instance")
)

Functions

func GetLexerJSON

func GetLexerJSON() (text string)

GetLexerJSON returns a JSON representation of the syntax lexer

func GetSyntaxEBNF

func GetSyntaxEBNF() (ebnf string)

GetSyntaxEBNF returns the EBNF text representing the Enjin Query Language

func PrepareSyntax

func PrepareSyntax(format string, argv ...interface{}) (prepared string, err error)

func SkipCreateIndex

func SkipCreateIndex(o *option) (err error)

func SkipCreateTable

func SkipCreateTable(o *option) (err error)

Types

type Boolean

type Boolean bool

func (*Boolean) Capture

func (b *Boolean) Capture(values []string) error

func (*Boolean) String

func (b *Boolean) String() string

type Condition

type Condition struct {
	Left  *Expression `parser:" '(' @@ ')'        " json:"left"`
	Type  string      `parser:" @( 'AND' | 'OR' ) " json:"type"`
	Right *Expression `parser:" '(' @@ ')'        " json:"right"`

	Pos lexer.Position
}

Condition is the AND/OR combining of two expressions

func (*Condition) String

func (c *Condition) String() (out string)

type Config

type Config struct {
	Prefix  string        `json:"prefix,omitempty"`
	Sources ConfigSources `json:"sources,omitempty"`
}

Config is the structure for configuring a New EnjinQL instance

Config structures can be constructed manually, simply instantiate the Go types and build the structure directly. To check for errors, call the Config.Validate method.

Another way to create Config structures is with JSON and using ParseConfig to both unmarshal and validate the resulting Config instance.

The last way is to use the builder methods in a long chain to build the Config programmatically

For example, to recreate a config with the default PageSource and others for the page titles and descriptions:

bePage, err := NewConfig("be_eql").    // start building a Config
    NewSource("page").                 // start building the SourceConfig
    AddStringValue("shasum", 10).      // add shasum column
    AddStringValue("language", 10).    // add language code column
    AddStringValue("type", 48).        // add page type column
    AddStringValue("url", 1024).       // add page URL path column
    AddStringValue("stub", -1).        // add page stub column
    DoneSource().                      // done making this particular source
    NewSource("extra").                // start another SourceConfig
    AddStringValue("title", 200).      // add the title column
    AddStringValue("description", -1). // add the description
    DoneSource().                      // done making this particular source
    Make()

func NewConfig

func NewConfig(prefix ...string) (c *Config)

NewConfig returns a new Config instance with only the given prefix set. All prefix values given are combined into a single snake_cased prefix string

func ParseConfig

func ParseConfig[V string | []byte](data V) (c *Config, err error)

ParseConfig unmarshalls the given JSON data into a new Config instance

func (*Config) AddSource

func (c *Config) AddSource(source *SourceConfig) *Config

func (*Config) Clone

func (c *Config) Clone() (cloned *Config)

func (*Config) Make

func (c *Config) Make() (config *Config, err error)

func (*Config) NewSource

func (c *Config) NewSource(name string) (source *SourceConfig)

func (*Config) Serialize

func (c *Config) Serialize() (output string)

Serialize is a convenience method for returning (unindented) JSON data representing this Config instance, use ParseConfig to restore the Config

func (*Config) String

func (c *Config) String() (output string)

String returns (indented) JSON data representing this Config instance, use ParseConfig to restore the Config

func (*Config) Validate

func (c *Config) Validate() (err error)

Validate checks the Config instance for any errors, returning the first one found

type ConfigSourceValues

type ConfigSourceValues []*SourceConfigValue

ConfigSourceValues is a slice type for providing Config builder methods

func (ConfigSourceValues) Clone

func (v ConfigSourceValues) Clone() (cloned ConfigSourceValues)
func (v ConfigSourceValues) HasLinks() (linked bool)

func (ConfigSourceValues) Names

func (v ConfigSourceValues) Names() (names []string)

type ConfigSources

type ConfigSources []*SourceConfig

ConfigSources is a slice type for providing Config builder methods

func (ConfigSources) Clone

func (s ConfigSources) Clone() (cloned ConfigSources)

func (ConfigSources) DataNames

func (s ConfigSources) DataNames() (names []string)

DataNames returns a list of all data source names (sources with no parent), in the order they were added

func (ConfigSources) Get

func (s ConfigSources) Get(name string) (cloned *SourceConfig)

Get returns a clone of the named SourceConfig

func (ConfigSources) JoinNames

func (s ConfigSources) JoinNames() (names []string)

JoinNames returns a list of all link source names (sources with a parent and has at least one value linked), in the order they were added

func (ConfigSources) LinkNames

func (s ConfigSources) LinkNames() (names []string)

LinkNames returns a list of all link source names (sources with a parent or has at least one value linked), in the order they were added

func (ConfigSources) Names

func (s ConfigSources) Names() (names []string)

Names returns a list of all source names, in the order they were added

type Constraint

type Constraint struct {
	Left   *SourceRef `parser:" @@                               " json:"left"`
	Op     *Operator  `parser:" (   ( @@                         " json:"op,omitempty"`
	Right  *Value     `parser:"       @@ )                       " json:"right,omitempty"`
	Not    bool       `parser:"   | ( @'NOT'?                    " json:"not,omitempty"`
	In     bool       `parser:"       @'IN'                      " json:"in,omitempty"`
	Values []*Value   `parser:"       '(' @@ ( ',' @@ )* ')' ) ) " json:"values,omitempty"`

	Pos lexer.Position
}

Constraint is the comparing of two values

func (*Constraint) String

func (c *Constraint) String() (out string)

type EnjinQL

type EnjinQL interface {

	// Parse parses the Enjin Query Language format string and constructs a
	// new Syntax instance
	Parse(format string, args ...interface{}) (parsed *Syntax, err error)

	// ParsedToSql prepares the SQL query arguments from a parsed Syntax tree
	ParsedToSql(parsed *Syntax) (query string, argv []interface{}, err error)

	// ToSQL uses Parse and ParsedToSQL to produce the SQL query arguments
	ToSQL(format string, args ...interface{}) (query string, argv []interface{}, err error)

	// Perform uses ToSQL to build and execute the SQL statement
	Perform(format string, argv ...interface{}) (columns []string, results context.Contexts, err error)

	// Plan uses Parse to prepare the Syntax tree, then prepares the SQL table
	// INNER JOIN statement plan and returns two summaries of the resulting
	// plan: a brief one-liner and a verbose multi-line
	Plan(format string, args ...interface{}) (brief, verbose string, err error)

	// DBH returns either the current sql.Tx or the default sql.DB instance
	DBH() SqlDB

	// T returns the sqlbuilder.Table associated with the named source,
	// returns nil if the table or source do not exist
	T(name string) (t sqlbuilder.Table)

	// SqlBuilder returns a sqlbuilder.Buildable instance, preconfigured with
	// the EnjinQL sqlbuilder.Dialect
	SqlBuilder() sqlbuilder.Buildable

	// SqlDialect returns the configured go-sqlbuilder dialect
	SqlDialect() sqlbuilder.Dialect

	// SqlBegin starts and returns new SQL transaction, this is the only way
	// to properly add or remove data from indexing
	SqlBegin() (tx SqlTrunkTX, err error)

	// SqlExec is a convenience wrapper around sql.DB.Exec which returns the
	// sql.Result values in one step
	SqlExec(query string, argv ...interface{}) (id int64, affected int64, err error)

	// SqlQuery is a convenience wrapper around sql.DB.Query which returns
	// the column order and results
	SqlQuery(query string, argv ...interface{}) (columns []string, results context.Contexts, err error)

	// String returns an indented JSON representation of the Config
	String() string
	// Marshal returns a compact JSON representation of the Config
	Marshal() (data []byte, err error)
	// Unmarshal always returns the ErrUnmarshalEnjinQL error
	Unmarshal(data []byte) (err error)

	// Config returns a clone of this EnjinQL instance's configuration
	Config() (cloned *Config)

	// CreateTables will process all configured sources and issue CREATE TABLE
	// IF NOT EXISTS queries, stopping at the first error
	CreateTables() (err error)

	// CreateIndexes will process all configured sources and issue CREATE
	// INDEX IF NOT EXISTS queries, stopping at the first error
	CreateIndexes() (err error)

	// Close calls the Close method on the sql.DB instance and flags this
	// enjinql instance as being closed
	Close() (err error)

	// Ready returns nil if this EnjinQL instance has an open sql.DB instance
	// and returns sql.ErrConnDone otherwise
	Ready() error
	// contains filtered or unexported methods
}

EnjinQL is the interface for a built enjinql instance

func New

func New(c *Config, dbh *sql.DB, dialect sqlbuilder.Dialect, options ...Option) (eql EnjinQL, err error)

type Expression

type Expression struct {
	Constraint *Constraint `parser:"   @@  " json:"operation,omitempty"`
	Condition  *Condition  `parser:" | @@  " json:"condition,omitempty"`

	Pos lexer.Position
}

func (*Expression) String

func (e *Expression) String() (out string)

type Null

type Null bool

func (*Null) Capture

func (n *Null) Capture(values []string) error

func (*Null) String

func (n *Null) String() string

type Operator

type Operator struct {
	EQ  bool `parser:" (   @'=='               " json:"eq,omitempty"`
	NE  bool `parser:"   | @( '!=' | '<>' )    " json:"ne,omitempty"`
	GE  bool `parser:"   | @'>='               " json:"ge,omitempty"`
	LE  bool `parser:"   | @'<='               " json:"le,omitempty"`
	GT  bool `parser:"   | @'>'                " json:"gt,omitempty"`
	LT  bool `parser:"   | @'<'                " json:"lt,omitempty"`
	Not bool `parser:" ) | ( (   @( 'NOT' )    " json:"not,omitempty"`
	Nt  bool `parser:"         | @( '!' )   )? " json:"nt,omitempty"`
	LK  bool `parser:"     (   @'LIKE'         " json:"lk,omitempty"`
	SW  bool `parser:"       | @'^='           " json:"sw,omitempty"`
	EW  bool `parser:"       | @'$='           " json:"ew,omitempty"`
	CS  bool `parser:"       | @'*='           " json:"cs,omitempty"`
	CF  bool `parser:"       | @'~='       ) ) " json:"cf,omitempty"`

	Pos lexer.Position
}

Operator represents a comparison operation

| Key |  Op  | Description              |
+-----+------+--------------------------+
| EQ  |  ==  | equal to                 |
| NE  |  !=  | not equal to             |
| GE  |  >=  | greater than or equal to |
| LE  |  <=  | less than or equal to    |
| GT  |  >   | greater than             |
| LT  |  <   | less than                |
| LK  | LIKE | like                     |
| SW  |  ^=  | starts with              |
| EW  |  $=  | ends with                |
| CS  |  *=  | contains one of string   |
| CF  |  ~=  | contains any of fields   |

For LK, SW, EW, CS and CF, there is a NOT modifier:

| Key |  Op  | Description              |
+-----+------+--------------------------+
| Not |  NOT | long-form negate         |
| Nt  |  !   | short-form negate        |

Example NOT modifier usage:

| Example | Description         |
+---------+---------------------+
| NOT ^=  | does not start with |
|   !$=   | does not end with   |

func (Operator) String

func (o Operator) String() string

type Option

type Option func(o *option) (err error)

type OrderBy

type OrderBy struct {
	Sources   *[]*SourceRef `parser:" 'ORDER' 'BY' (   @@ ( ',' @@ )*          " json:"key"`
	Random    *bool         `parser:"                | @( 'RANDOM' '(' ')' ) ) " json:"random,omitempty"`
	Direction *string       `parser:" @( 'ASC' | 'DSC' | 'DESC' )?             " json:"dir,omitempty"`

	Pos lexer.Position
}

func (*OrderBy) IsDESC

func (o *OrderBy) IsDESC() bool

func (*OrderBy) String

func (o *OrderBy) String() (out string)

type Shell

type Shell interface {
	// Run starts the interactive shell
	Run()
	// Stop stops the interactive shell
	Stop()
	// Close shuts down the shell completely
	Close()
	// Process runs shell using arguments in non-interactive mode
	Process(argv ...string) (err error)
}

Shell is a simple interface for managing an interactive eql shell session

func NewShell

func NewShell(eql EnjinQL, shell *ishell.Shell) Shell

NewShell starts a new EnjinQL interactive shell, creating a new default shell configuration if the shell argument is nil

type SourceConfig

type SourceConfig struct {
	Name   string             `json:"name"`
	Parent *string            `json:"parent,omitempty"`
	Values ConfigSourceValues `json:"values"`
	Unique [][]string         `json:"unique,omitempty"`
	Index  [][]string         `json:"index,omitempty"`
	// contains filtered or unexported fields
}

SourceConfig is the structure for configuring a specific source

func MakeSourceConfig

func MakeSourceConfig(parent, name string, primary *SourceConfigValue, additional ...*SourceConfigValue) (sc *SourceConfig)

MakeSourceConfig is a convenience wrapper for constructing SourceConfig instances. Will panic if the primary value is nil.

func PagePermalinkSourceConfig

func PagePermalinkSourceConfig() (sc *SourceConfig)

func PageRedirectSourceConfig

func PageRedirectSourceConfig() (sc *SourceConfig)

func PageSourceConfig

func PageSourceConfig() (sc *SourceConfig)

PageSourceConfig returns a new SourceConfig, preset with the primary source settings required for the Go-Enjin project. The page SourceConfig is preset with five columns in addition to the default id column present with all sources:

+------+-----------+-------------------------------------------+
| size | column    | description                               |
+------+-----------+-------------------------------------------+
|  10  | shasum    | a unique identifier used within Go-Enjin  |
|  10  | language  | the language code for a page              |
|  64  | type      | the type of page                          |
|  64  | archetype | the page archetype                        |
| 2000 | url       | the URL path (absolute) to a page         |
|  -1  | stub      | JSON context for filesystem lookup        |
+------+-----------+-------------------------------------------+

func (*SourceConfig) AddIndex

func (sc *SourceConfig) AddIndex(keys ...string) *SourceConfig

AddIndex adds the given keys to the list of indexes

func (*SourceConfig) AddUnique

func (sc *SourceConfig) AddUnique(keys ...string) *SourceConfig

AddUnique add the given keys to the list of unique constraints

func (*SourceConfig) AddValue

func (sc *SourceConfig) AddValue(v *SourceConfigValue) *SourceConfig

AddValue adds the given SourceConfigValue

func (*SourceConfig) Clone

func (sc *SourceConfig) Clone() (cloned *SourceConfig)

func (*SourceConfig) DoneSource

func (sc *SourceConfig) DoneSource() *Config

DoneSource completes this SourceConfig builder chain

func (*SourceConfig) NewBoolValue

func (sc *SourceConfig) NewBoolValue(key string) *SourceConfig

func (*SourceConfig) NewFloatValue

func (sc *SourceConfig) NewFloatValue(key string) *SourceConfig

func (*SourceConfig) NewIntValue

func (sc *SourceConfig) NewIntValue(key string) *SourceConfig

func (*SourceConfig) NewLinkedValue

func (sc *SourceConfig) NewLinkedValue(table, key string) *SourceConfig

NewLinkedValue adds a cross-table link to another source

func (*SourceConfig) NewStringValue

func (sc *SourceConfig) NewStringValue(key string, size int) *SourceConfig

NewStringValue adds a string value column with the given size. If the size is less-than or equal-to zero, the column will be some sort of TEXT type depending on the specific SQL service used

func (*SourceConfig) SetParent

func (sc *SourceConfig) SetParent(name string) *SourceConfig

SetParent configures the SourceConfig.Parent setting

func (*SourceConfig) Type

func (sc *SourceConfig) Type() (t SourceConfigType)

type SourceConfigType

type SourceConfigType uint8
const (
	UnknownSourceType SourceConfigType = iota
	// DataSourceType represents sources that have no parent and no linked values
	DataSourceType
	// LinkSourceType represents sources that have a parent or have linked values
	LinkSourceType
	// JoinSourceType represents sources that have a parent and have liked values
	JoinSourceType
)

func (SourceConfigType) String

func (t SourceConfigType) String() (name string)

type SourceConfigValue

type SourceConfigValue struct {
	Int    *SourceConfigValueInt    `json:"int,omitempty"`
	Bool   *SourceConfigValueBool   `json:"bool,omitempty"`
	Time   *SourceConfigValueTime   `json:"time,omitempty"`
	Float  *SourceConfigValueFloat  `json:"float,omitempty"`
	String *SourceConfigValueString `json:"string,omitempty"`
	Linked *SourceConfigValueLinked `json:"linked,omitempty"`
	// contains filtered or unexported fields
}

SourceConfigValue is the structure for configuring a specific value indexed by the SourceConfig

func NewBoolValue

func NewBoolValue(key string) *SourceConfigValue

NewBoolValue is a convenience wrapper to construct a boolean SourceConfigValue

func NewFloatValue

func NewFloatValue(key string) *SourceConfigValue

NewFloatValue is a convenience wrapper to construct a decimal SourceConfigValue

func NewIntValue

func NewIntValue(key string) *SourceConfigValue

NewIntValue is a convenience wrapper to construct an integer SourceConfigValue

func NewLinkedValue

func NewLinkedValue(source, key string) *SourceConfigValue

NewLinkedValue is a convenience wrapper to construct a linked SourceConfigValue

func NewStringValue

func NewStringValue(key string, size int) *SourceConfigValue

NewStringValue is a convenience wrapper to construct a string SourceConfigValue

func NewTimeValue

func NewTimeValue(key string) *SourceConfigValue

NewTimeValue is a convenience wrapper to construct a boolean SourceConfigValue

func (*SourceConfigValue) Clone

func (scv *SourceConfigValue) Clone() (cloned *SourceConfigValue)

func (*SourceConfigValue) Name

func (scv *SourceConfigValue) Name() (output string)

type SourceConfigValueBool

type SourceConfigValueBool struct {
	Key string `json:"key"`
	// contains filtered or unexported fields
}

type SourceConfigValueFloat

type SourceConfigValueFloat struct {
	Key string `json:"key"`
	// contains filtered or unexported fields
}

type SourceConfigValueInt

type SourceConfigValueInt struct {
	Key string `json:"key"`
	// contains filtered or unexported fields
}

type SourceConfigValueLinked

type SourceConfigValueLinked struct {
	Source string `json:"table"`
	Key    string `json:"key"`
	// contains filtered or unexported fields
}

type SourceConfigValueString

type SourceConfigValueString struct {
	Key  string `json:"key"`
	Size int    `json:"size"`
	// contains filtered or unexported fields
}

type SourceConfigValueTime

type SourceConfigValueTime struct {
	Key string `json:"key"`
	// contains filtered or unexported fields
}

type SourceKey

type SourceKey struct {
	Source *string `parser:" ( @Ident (?= '.' ) )? " json:"source,omitempty"`
	Key    string  `parser:" '.' @Ident            " json:"key"`
	Alias  *string `parser:" ( 'AS' @Ident )?      " json:"alias,omitempty"`

	Pos lexer.Position
}

func (*SourceKey) AsKey

func (s *SourceKey) AsKey() (sk *SrcKey)

func (*SourceKey) String

func (s *SourceKey) String() (src string)

type SourceRef

type SourceRef struct {
	Source *string `parser:"   ( ( @Ident (?= '.' ) )?     " json:"source,omitempty"`
	Key    *string `parser:"     '.' @Ident            )   " json:"key,omitempty"`
	Alias  *string `parser:"   | @Ident                    " json:"alias,omitempty"`

	Pos lexer.Position
}

func (*SourceRef) String

func (s *SourceRef) String() string

type SqlDB

type SqlDB interface {
	Perform(format string, argv ...interface{}) (columns []string, results clContext.Contexts, err error)
	PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
	Prepare(query string) (*sql.Stmt, error)
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
	Exec(query string, args ...any) (sql.Result, error)
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
	Query(query string, args ...any) (*sql.Rows, error)
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
	QueryRow(query string, args ...any) *sql.Row
}

type SqlTX

type SqlTX interface {
	SqlDB

	Insert(name string, values ...interface{}) (id int64, err error)
	Delete(name string, id int64) (affected int64, err error)
	DeleteWhereEQ(sourceName, key string, value interface{}) (affected int64, err error)
}

type SqlTrunkTX

type SqlTrunkTX interface {
	SqlTX

	TX() SqlTX

	Valid() bool
	Commit() (err error)
	Rollback() (err error)
}

type SrcKey

type SrcKey struct {
	Src   string
	Key   string
	Alias string
}

func (*SrcKey) String

func (s *SrcKey) String() string

type Syntax

type Syntax struct {
	Lookup    bool         `parser:" ( ( @'LOOKUP'        " json:"lookup,omitempty"`
	Count     bool         `parser:"     @'COUNT'?        " json:"count,omitempty"`
	Distinct  bool         `parser:"     @'DISTINCT'?     " json:"distinct,omitempty"`
	Keys      []*SourceKey `parser:"     @@ ( ',' @@ )* ) " json:"keys,omitempty"`
	Query     bool         `parser:"   | @'QUERY' )       " json:"query,omitempty"`
	Within    *Expression  `parser:" ( 'WITHIN' @@ )?     " json:"within,omitempty"`
	OrderBy   *OrderBy     `parser:" ( @@ )?              " json:"orderBy,omitempty"`
	Offset    *int         `parser:" ( 'OFFSET' @Int )?   " json:"offset,omitempty"`
	Limit     *int         `parser:" ( 'LIMIT' @Int )?    " json:"limit,omitempty"`
	Semicolon bool         `parser:" ( @';' )?            " json:"semicolon,omitempty"`

	Pos lexer.Position
}

func ParseSyntax

func ParseSyntax[V []byte | string](input V) (parsed *Syntax, err error)

ParseSyntax parses the input string and returns an initialized Syntax tree

func (*Syntax) String

func (s *Syntax) String() string

func (*Syntax) Validate

func (s *Syntax) Validate() (err error)

type SyntaxError

type SyntaxError struct {
	Pos      lexer.Position
	Parent   error
	Specific error
}

func (*SyntaxError) Err

func (e *SyntaxError) Err() error

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

type Value

type Value struct {
	Text        *string    `parser:"   @String                 " json:"text,omitempty"`
	Int         *int       `parser:" | @Int                    " json:"int,omitempty"`
	Float       *float64   `parser:" | @Float                  " json:"float,omitempty"`
	Bool        *Boolean   `parser:" | @( 'TRUE' | 'FALSE' )   " json:"bool,omitempty"`
	Null        *Null      `parser:" | @( 'NIL'  | 'NULL'  )   " json:"nil,omitempty"`
	SourceRef   *SourceRef `parser:" | @@                      " json:"source,omitempty"`
	Placeholder *string    `parser:" | @Placeholder            " json:"placeholder,omitempty"`

	Pos lexer.Position
}

func (*Value) String

func (v *Value) String() (out string)

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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