| // 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. |
| |
| package query_parser |
| |
| import ( |
| "fmt" |
| "math/big" |
| "regexp" |
| "strconv" |
| "strings" |
| "text/scanner" |
| "time" |
| "unicode/utf8" |
| |
| ds "v.io/v23/query/engine/datasource" |
| "v.io/v23/query/syncql" |
| "v.io/v23/vdl" |
| ) |
| |
| type TokenType int |
| |
| const ( |
| TokCHAR TokenType = 1 + iota |
| TokCOMMA |
| TokEOF |
| TokEQUAL |
| TokFLOAT |
| TokIDENT |
| TokINT |
| TokLEFTANGLEBRACKET |
| TokLEFTBRACKET |
| TokLEFTPAREN |
| TokMINUS |
| TokParameter // allowed only in prepared statements |
| TokPERIOD |
| TokRIGHTANGLEBRACKET |
| TokRIGHTBRACKET |
| TokRIGHTPAREN |
| TokSTRING |
| TokERROR |
| ) |
| |
| const ( |
| MaxStatementLen = 10000 |
| ) |
| |
| type Token struct { |
| Tok TokenType |
| Value string |
| Off int64 |
| } |
| |
| type Node struct { |
| Off int64 |
| } |
| |
| type Statement interface { |
| Offset() int64 |
| String() string |
| CopyAndSubstitute(db ds.Database, paramValues []*vdl.Value) (Statement, error) |
| } |
| |
| type Segment struct { |
| Value string |
| Keys []*Operand // Used as key(s) or index(es) to dereference map/set/array/list. |
| Node |
| } |
| |
| type Field struct { |
| Segments []Segment |
| Node |
| } |
| |
| type BinaryOperatorType int |
| |
| const ( |
| And BinaryOperatorType = 1 + iota |
| Equal |
| GreaterThan |
| GreaterThanOrEqual |
| Is |
| IsNot |
| LessThan |
| LessThanOrEqual |
| Like |
| NotEqual |
| NotLike |
| Or |
| ) |
| |
| type BinaryOperator struct { |
| Type BinaryOperatorType |
| Node |
| } |
| |
| type OperandType int |
| |
| const ( |
| TypBigInt OperandType = 1 + iota // Only as a result of Resolve/Coerce Operand |
| TypBigRat // Only as a result of Resolve/Coerce Operand |
| TypBool |
| TypComplex |
| TypExpr |
| TypField |
| TypFloat |
| TypFunction |
| TypInt |
| TypNil |
| TypParameter |
| TypStr |
| TypTime |
| TypObject // Only as the result of a ResolveOperand. |
| TypUint // Only as a result of a ResolveOperand |
| ) |
| |
| type Operand struct { |
| Type OperandType |
| BigInt *big.Int |
| BigRat *big.Rat |
| Bool bool |
| Complex complex128 |
| Column *Field |
| Float float64 |
| Function *Function |
| Int int64 |
| Str string |
| Time time.Time |
| Prefix string // Computed by checker for Like expressions |
| Regex string // Computed by checker for Like expressions |
| Uint uint64 |
| CompRegex *regexp.Regexp |
| Expr *Expression |
| Object *vdl.Value |
| Node |
| } |
| |
| type Function struct { |
| Name string |
| Args []*Operand |
| ArgTypes []OperandType // Filled in by checker. |
| RetType OperandType // Filled in by checker. |
| Computed bool // Checker sets to true and sets RetValue if function takes no args |
| RetValue *Operand |
| Node |
| } |
| |
| type Expression struct { |
| Operand1 *Operand |
| Operator *BinaryOperator |
| Operand2 *Operand |
| Node |
| } |
| |
| type SelectorType int |
| |
| const ( |
| TypSelField SelectorType = 1 + iota |
| TypSelFunc |
| ) |
| |
| // Selector: entries in the select clause. |
| // Entries can be functions for fields. |
| // The AS name, if present, will ONLY be used in the |
| // returned column header. |
| type Selector struct { |
| Type SelectorType |
| Field *Field |
| Function *Function |
| As *AsClause // If not nil, used in returned column header. |
| Node |
| } |
| |
| type AsClause struct { |
| AltName Name |
| Node |
| } |
| |
| type Name struct { |
| Value string |
| Node |
| } |
| |
| type SelectClause struct { |
| Selectors []Selector |
| Node |
| } |
| |
| type FromClause struct { |
| Table TableEntry |
| Node |
| } |
| |
| type TableEntry struct { |
| Name string |
| DBTable ds.Table // Checker gets table from db and sets this. |
| Node |
| } |
| |
| type WhereClause struct { |
| Expr *Expression |
| Node |
| } |
| |
| type CharValue struct { |
| Value rune |
| Node |
| } |
| |
| type Int64Value struct { |
| Value int64 |
| Node |
| } |
| |
| type EscapeClause struct { |
| EscapeChar *CharValue |
| Node |
| } |
| |
| type LimitClause struct { |
| Limit *Int64Value |
| Node |
| } |
| |
| type ResultsOffsetClause struct { |
| ResultsOffset *Int64Value |
| Node |
| } |
| |
| type SelectStatement struct { |
| Select *SelectClause |
| From *FromClause |
| Where *WhereClause |
| Escape *EscapeClause |
| Limit *LimitClause |
| ResultsOffset *ResultsOffsetClause |
| Node |
| } |
| |
| type DeleteStatement struct { |
| From *FromClause |
| Where *WhereClause |
| Escape *EscapeClause |
| Limit *LimitClause |
| Node |
| } |
| |
| func scanToken(s *scanner.Scanner) *Token { |
| // TODO(jkline): Replace golang text/scanner. |
| var token Token |
| tok := s.Scan() |
| token.Value = s.TokenText() |
| token.Off = int64(s.Position.Offset) |
| |
| if s.ErrorCount > 0 { |
| token.Tok = TokERROR |
| return &token |
| } |
| |
| switch tok { |
| case '.': |
| token.Tok = TokPERIOD |
| case ',': |
| token.Tok = TokCOMMA |
| case '-': |
| token.Tok = TokMINUS |
| case '(': |
| token.Tok = TokLEFTPAREN |
| case ')': |
| token.Tok = TokRIGHTPAREN |
| case '=': |
| token.Tok = TokEQUAL |
| case '<': |
| token.Tok = TokLEFTANGLEBRACKET |
| case '>': |
| token.Tok = TokRIGHTANGLEBRACKET |
| case '[': |
| token.Tok = TokLEFTBRACKET |
| case ']': |
| token.Tok = TokRIGHTBRACKET |
| case '?': |
| token.Tok = TokParameter |
| case scanner.EOF: |
| token.Tok = TokEOF |
| case scanner.Ident: |
| token.Tok = TokIDENT |
| case scanner.Int: |
| token.Tok = TokINT |
| case scanner.Float: |
| token.Tok = TokFLOAT |
| case scanner.Char: |
| token.Tok = TokCHAR |
| token.Value = token.Value[1 : len(token.Value)-1] |
| case scanner.String: |
| token.Tok = TokSTRING |
| token.Value = token.Value[1 : len(token.Value)-1] |
| } |
| return &token |
| } |
| |
| // Text/scanner reports errors to stderr that are not errors in the query language. |
| // For example, to get the value where the key is "\", |
| // One would write the string: |
| // "select v where k = \"\\\"" |
| // This will result in the scanner spewing "literal not terminated" to stderr if we don't |
| // set the Error field in Scanner. As such, Error is set to the following function which |
| // eats errors. In the longer term, there is still a TODO to replace text/scanner with |
| // our own scanner. |
| func scannerError(s *scanner.Scanner, msg string) { |
| // Do nothing. |
| } |
| |
| // Parse a statement. Return it or an error. |
| func Parse(db ds.Database, src string) (*Statement, error) { |
| if len(src) > MaxStatementLen { |
| return nil, syncql.NewErrMaxStatementLenExceeded(db.GetContext(), int64(0), MaxStatementLen, int64(len(src))) |
| } |
| r := strings.NewReader(src) |
| var s scanner.Scanner |
| s.Init(r) |
| s.Error = scannerError |
| |
| token := scanToken(&s) // eat the select |
| if token.Tok == TokEOF { |
| return nil, syncql.NewErrNoStatementFound(db.GetContext(), token.Off) |
| } |
| if token.Tok != TokIDENT { |
| return nil, syncql.NewErrExpectedIdentifier(db.GetContext(), token.Off, token.Value) |
| } |
| switch strings.ToLower(token.Value) { |
| case "select": |
| var st Statement |
| var err error |
| st, token, err = selectStatement(db, &s, token) |
| return &st, err |
| case "delete": |
| var st Statement |
| var err error |
| st, token, err = deleteStatement(db, &s, token) |
| return &st, err |
| default: |
| return nil, syncql.NewErrUnknownIdentifier(db.GetContext(), token.Off, token.Value) |
| } |
| } |
| |
| // Parse select. |
| func selectStatement(db ds.Database, s *scanner.Scanner, token *Token) (Statement, *Token, error) { |
| var st SelectStatement |
| st.Off = token.Off |
| |
| // parse SelectClause |
| var err error |
| st.Select, token, err = parseSelectClause(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| st.From, token, err = parseFromClause(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| st.Where, token, err = parseWhereClause(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| st.Escape, st.Limit, st.ResultsOffset, token, err = parseEscapeLimitResultsOffsetClauses(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // There can be nothing remaining for the current statement |
| if token.Tok != TokEOF { |
| return nil, nil, syncql.NewErrUnexpected(db.GetContext(), token.Off, token.Value) |
| } |
| |
| return st, token, nil |
| } |
| |
| // Parse delete. |
| func deleteStatement(db ds.Database, s *scanner.Scanner, token *Token) (Statement, *Token, error) { |
| var st DeleteStatement |
| st.Off = token.Off |
| |
| token = scanToken(s) // eat the delete |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| |
| // parse FromClause |
| var err error |
| st.From, token, err = parseFromClause(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // parse WhereClause |
| st.Where, token, err = parseWhereClause(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| st.Escape, st.Limit, token, err = parseEscapeLimitClauses(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // There can be nothing remaining for the current statement |
| if token.Tok != TokEOF { |
| return nil, nil, syncql.NewErrUnexpected(db.GetContext(), token.Off, token.Value) |
| } |
| |
| return st, token, nil |
| } |
| |
| // Parse the select clause (fields). Return *SelectClause, next token (or error). |
| func parseSelectClause(db ds.Database, s *scanner.Scanner, token *Token) (*SelectClause, *Token, error) { |
| // must be at least one selector or it is an error |
| // field seclectors may be in dot notation |
| // selectors are separated by commas |
| var selectClause SelectClause |
| selectClause.Off = token.Off |
| token = scanToken(s) // eat the select |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| var err error |
| // scan first selector |
| if token, err = parseSelector(db, s, &selectClause, token); err != nil { |
| return nil, nil, err |
| } |
| |
| // More selectors? |
| for token.Tok == TokCOMMA { |
| token = scanToken(s) |
| if token, err = parseSelector(db, s, &selectClause, token); err != nil { |
| return nil, nil, err |
| } |
| } |
| |
| return &selectClause, token, nil |
| } |
| |
| // Parse a selector. Return next token (or error). |
| func parseSelector(db ds.Database, s *scanner.Scanner, selectClause *SelectClause, token *Token) (*Token, error) { |
| if token.Tok != TokIDENT { |
| return nil, syncql.NewErrExpectedIdentifier(db.GetContext(), token.Off, token.Value) |
| } |
| |
| var selector Selector |
| selector.Off = token.Off |
| selector.Type = TypSelField |
| var field Field |
| selector.Field = &field |
| selector.Field.Off = token.Off |
| selector.Field = &field |
| |
| var segment *Segment |
| var err error |
| if segment, token, err = parseSegment(db, s, token); err != nil { |
| return nil, err |
| } |
| selector.Field.Segments = append(selector.Field.Segments, *segment) |
| |
| // It might be a function. |
| if token.Tok == TokLEFTPAREN { |
| // Segments with a key(s) specified cannot be function calls. |
| if len(segment.Keys) != 0 { |
| return nil, syncql.NewErrUnexpected(db.GetContext(), token.Off, token.Value) |
| } |
| // switch selector to a function |
| selector.Type = TypSelFunc |
| var err error |
| if selector.Function, token, err = parseFunction(db, s, segment.Value, segment.Off, token); err != nil { |
| return nil, err |
| } |
| selector.Field = nil |
| |
| } else { |
| for token.Tok != TokEOF && token.Tok == TokPERIOD { |
| token = scanToken(s) |
| if token.Tok != TokIDENT { |
| return nil, syncql.NewErrExpectedIdentifier(db.GetContext(), token.Off, token.Value) |
| } |
| if segment, token, err = parseSegment(db, s, token); err != nil { |
| return nil, err |
| } |
| selector.Field.Segments = append(selector.Field.Segments, *segment) |
| } |
| } |
| |
| // Check for AS |
| if token.Tok == TokIDENT && strings.ToLower(token.Value) == "as" { |
| var asClause AsClause |
| asClause.Off = token.Off |
| token = scanToken(s) |
| if token.Tok != TokIDENT { |
| return nil, syncql.NewErrExpectedIdentifier(db.GetContext(), token.Off, token.Value) |
| } |
| asClause.AltName.Value = token.Value |
| asClause.AltName.Off = token.Off |
| selector.As = &asClause |
| token = scanToken(s) |
| } |
| |
| selectClause.Selectors = append(selectClause.Selectors, selector) |
| return token, nil |
| } |
| |
| // Parse a segment. Return the segment and the next token (or error). |
| // Check for a key (i.e., [<key>] following the segment). |
| func parseSegment(db ds.Database, s *scanner.Scanner, token *Token) (*Segment, *Token, error) { |
| var segment Segment |
| segment.Value = token.Value |
| segment.Off = token.Off |
| token = scanToken(s) |
| |
| for token.Tok == TokLEFTBRACKET { |
| // A key to the segment is specified. |
| token = scanToken(s) |
| var key *Operand |
| var err error |
| key, token, err = parseOperand(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| segment.Keys = append(segment.Keys, key) |
| if token.Tok != TokRIGHTBRACKET { |
| return nil, nil, syncql.NewErrExpected(db.GetContext(), token.Off, "]") |
| } |
| token = scanToken(s) |
| } |
| return &segment, token, nil |
| } |
| |
| // Parse the from clause, Return FromClause and next Token or error. |
| func parseFromClause(db ds.Database, s *scanner.Scanner, token *Token) (*FromClause, *Token, error) { |
| if strings.ToLower(token.Value) != "from" { |
| return nil, nil, syncql.NewErrExpectedFrom(db.GetContext(), token.Off, token.Value) |
| } |
| var fromClause FromClause |
| fromClause.Off = token.Off |
| token = scanToken(s) // eat from |
| // must be a table specified |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| if token.Tok != TokIDENT { |
| return nil, nil, syncql.NewErrExpectedIdentifier(db.GetContext(), token.Off, token.Value) |
| } |
| fromClause.Table.Off = token.Off |
| fromClause.Table.Name = token.Value |
| token = scanToken(s) |
| return &fromClause, token, nil |
| } |
| |
| // Parse the where clause (if any). Return WhereClause (could be nil) and and next Token or error. |
| func parseWhereClause(db ds.Database, s *scanner.Scanner, token *Token) (*WhereClause, *Token, error) { |
| // parse Optional where clause |
| if token.Tok != TokEOF { |
| if strings.ToLower(token.Value) != "where" { |
| return nil, token, nil |
| } |
| var where WhereClause |
| where.Off = token.Off |
| token = scanToken(s) |
| // parse expression |
| var err error |
| where.Expr, token, err = parseExpression(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| return &where, token, nil |
| } else { |
| return nil, token, nil |
| } |
| } |
| |
| // Parse a parenthesized expression. Return expression and next token (or error) |
| func parseParenthesizedExpression(db ds.Database, s *scanner.Scanner, token *Token) (*Expression, *Token, error) { |
| // Only called when token == TokLEFTPAREN |
| token = scanToken(s) // eat '(' |
| var expr *Expression |
| var err error |
| expr, token, err = parseExpression(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| // Expect right paren |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| if token.Tok != TokRIGHTPAREN { |
| return nil, nil, syncql.NewErrExpected(db.GetContext(), token.Off, ")") |
| } |
| token = scanToken(s) // eat ')' |
| return expr, token, nil |
| } |
| |
| // Parse an expression. Return expression and next token (or error) |
| func parseExpression(db ds.Database, s *scanner.Scanner, token *Token) (*Expression, *Token, error) { |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| |
| var err error |
| var expr *Expression |
| |
| if token.Tok == TokLEFTPAREN { |
| expr, token, err = parseParenthesizedExpression(db, s, token) |
| } else { |
| // We expect a like/equal expression |
| expr, token, err = parseLikeEqualExpression(db, s, token) |
| } |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| for token.Tok != TokEOF && token.Tok != TokRIGHTPAREN { |
| // There is more. If not 'and', 'or' or ')', the where is over. |
| if strings.ToLower(token.Value) != "and" && strings.ToLower(token.Value) != "or" { |
| return expr, token, nil |
| } |
| var newExpression Expression |
| var operand1 Operand |
| operand1.Type = TypExpr |
| operand1.Expr = expr |
| operand1.Off = operand1.Expr.Off |
| newExpression.Operand1 = &operand1 |
| newExpression.Off = operand1.Off |
| |
| newExpression.Operator, token, err = parseLogicalOperator(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| expr = &newExpression |
| // Need to set operand2. |
| var operand2 Operand |
| expr.Operand2 = &operand2 |
| if token.Tok == TokLEFTPAREN { |
| expr.Operand2.Type = TypExpr |
| expr.Operand2.Expr, token, err = parseParenthesizedExpression(db, s, token) |
| } else { |
| expr.Operand2.Type = TypExpr |
| expr.Operand2.Expr, token, err = parseLikeEqualExpression(db, s, token) |
| } |
| if err != nil { |
| return nil, nil, err |
| } |
| expr.Operand2.Off = expr.Operand2.Expr.Off |
| } |
| |
| return expr, token, nil |
| } |
| |
| // Parse a binary expression. Return expression and next token (or error) |
| func parseLikeEqualExpression(db ds.Database, s *scanner.Scanner, token *Token) (*Expression, *Token, error) { |
| var expression Expression |
| expression.Off = token.Off |
| |
| // operand 1 |
| var operand1 *Operand |
| var err error |
| operand1, token, err = parseOperand(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // operator |
| var operator *BinaryOperator |
| operator, token, err = parseBinaryOperator(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // operand 2 |
| var operand2 *Operand |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| operand2, token, err = parseOperand(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| expression.Operand1 = operand1 |
| expression.Operator = operator |
| expression.Operand2 = operand2 |
| |
| return &expression, token, nil |
| } |
| |
| func parseFunction(db ds.Database, s *scanner.Scanner, funcName string, funcOffset int64, token *Token) (*Function, *Token, error) { |
| var function Function |
| function.Name = funcName |
| function.Off = funcOffset |
| token = scanToken(s) // eat left paren |
| for token.Tok != TokRIGHTPAREN { |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrExpected(db.GetContext(), token.Off, ")") |
| } |
| var arg *Operand |
| var err error |
| arg, token, err = parseOperand(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| function.Args = append(function.Args, arg) |
| // A comma or right paren is expected, but a right paren cannot come after a comma. |
| if token.Tok == TokCOMMA { |
| token = scanToken(s) |
| if token.Tok == TokRIGHTPAREN { |
| // right paren cannot come after a comma |
| return nil, nil, syncql.NewErrExpectedOperand(db.GetContext(), token.Off, token.Value) |
| } |
| } else if token.Tok != TokRIGHTPAREN { |
| return nil, nil, syncql.NewErrUnexpected(db.GetContext(), token.Off, token.Value) |
| } |
| } |
| token = scanToken(s) // eat right paren |
| return &function, token, nil |
| } |
| |
| // Parse an operand (field or literal) and return it and the next Token (or error) |
| func parseOperand(db ds.Database, s *scanner.Scanner, token *Token) (*Operand, *Token, error) { |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| var operand Operand |
| operand.Off = token.Off |
| switch token.Tok { |
| case TokIDENT: |
| operand.Type = TypField |
| var field Field |
| field.Off = token.Off |
| var segment *Segment |
| var err error |
| if segment, token, err = parseSegment(db, s, token); err != nil { |
| return nil, nil, err |
| } |
| field.Segments = append(field.Segments, *segment) |
| |
| // If the next token is not a period, check for true/false/nil. |
| // If true/false or nil, change this operand to a bool or nil, respectively. |
| // Also, check for function call. If so, change to a function operand. |
| if token.Tok != TokPERIOD && (strings.ToLower(segment.Value) == "true" || strings.ToLower(segment.Value) == "false") { |
| operand.Type = TypBool |
| operand.Bool = strings.ToLower(segment.Value) == "true" |
| } else if token.Tok != TokPERIOD && strings.ToLower(segment.Value) == "nil" { |
| operand.Type = TypNil |
| } else if token.Tok == TokLEFTPAREN { |
| // Segments with a key specified cannot be function calls. |
| if len(segment.Keys) != 0 { |
| return nil, nil, syncql.NewErrUnexpected(db.GetContext(), token.Off, token.Value) |
| } |
| operand.Type = TypFunction |
| var err error |
| if operand.Function, token, err = parseFunction(db, s, segment.Value, segment.Off, token); err != nil { |
| return nil, nil, err |
| } |
| } else { // This is a field (column) operand. |
| // If the next token is a period, collect the rest of the segments in the column. |
| for token.Tok != TokEOF && token.Tok == TokPERIOD { |
| token = scanToken(s) |
| if token.Tok != TokIDENT { |
| return nil, nil, syncql.NewErrExpectedIdentifier(db.GetContext(), token.Off, token.Value) |
| } |
| if segment, token, err = parseSegment(db, s, token); err != nil { |
| return nil, nil, err |
| } |
| field.Segments = append(field.Segments, *segment) |
| } |
| operand.Column = &field |
| } |
| case TokINT: |
| operand.Type = TypInt |
| i, err := strconv.ParseInt(token.Value, 0, 64) |
| if err != nil { |
| return nil, nil, syncql.NewErrCouldNotConvert(db.GetContext(), token.Off, token.Value, "int64") |
| } |
| operand.Int = i |
| token = scanToken(s) |
| case TokFLOAT: |
| operand.Type = TypFloat |
| f, err := strconv.ParseFloat(token.Value, 64) |
| if err != nil { |
| return nil, nil, syncql.NewErrCouldNotConvert(db.GetContext(), token.Off, token.Value, "float64") |
| } |
| operand.Float = f |
| token = scanToken(s) |
| case TokCHAR: |
| operand.Type = TypInt |
| ch, _ := utf8.DecodeRuneInString(token.Value) |
| operand.Int = int64(ch) |
| token = scanToken(s) |
| case TokSTRING: |
| operand.Type = TypStr |
| operand.Str = token.Value |
| token = scanToken(s) |
| case TokMINUS: |
| // Could be negative int or negative float |
| off := token.Off |
| token = scanToken(s) |
| switch token.Tok { |
| case TokINT: |
| operand.Type = TypInt |
| i, err := strconv.ParseInt("-"+token.Value, 0, 64) |
| if err != nil { |
| return nil, nil, syncql.NewErrCouldNotConvert(db.GetContext(), off, "-"+token.Value, "int64") |
| } |
| operand.Int = i |
| case TokFLOAT: |
| operand.Type = TypFloat |
| f, err := strconv.ParseFloat("-"+token.Value, 64) |
| if err != nil { |
| return nil, nil, syncql.NewErrCouldNotConvert(db.GetContext(), off, "-"+token.Value, "float64") |
| } |
| operand.Float = f |
| default: |
| return nil, nil, syncql.NewErrExpected(db.GetContext(), token.Off, "int or float") |
| } |
| token = scanToken(s) |
| case TokParameter: |
| operand.Type = TypParameter |
| token = scanToken(s) |
| default: |
| return nil, nil, syncql.NewErrExpectedOperand(db.GetContext(), token.Off, token.Value) |
| } |
| return &operand, token, nil |
| } |
| |
| // Parse binary operator and return it and the next Token (or error) |
| func parseBinaryOperator(db ds.Database, s *scanner.Scanner, token *Token) (*BinaryOperator, *Token, error) { |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| var operator BinaryOperator |
| operator.Off = token.Off |
| if token.Tok == TokIDENT { |
| switch strings.ToLower(token.Value) { |
| case "equal": |
| operator.Type = Equal |
| token = scanToken(s) |
| case "is": |
| operator.Type = Is |
| token = scanToken(s) |
| // if the next token is "not", change to IsNot |
| if token.Tok != TokEOF && strings.ToLower(token.Value) == "not" { |
| operator.Type = IsNot |
| token = scanToken(s) |
| } |
| case "like": |
| operator.Type = Like |
| token = scanToken(s) |
| case "not": |
| token = scanToken(s) |
| if token.Tok == TokEOF || (strings.ToLower(token.Value) != "equal" && strings.ToLower(token.Value) != "like") { |
| return nil, nil, syncql.NewErrExpected(db.GetContext(), token.Off, "'equal' or 'like'") |
| } |
| switch strings.ToLower(token.Value) { |
| case "equal": |
| operator.Type = NotEqual |
| default: //case "like": |
| operator.Type = NotLike |
| } |
| token = scanToken(s) |
| default: |
| return nil, nil, syncql.NewErrExpectedOperator(db.GetContext(), token.Off, token.Value) |
| } |
| } else { |
| switch token.Tok { |
| case TokEQUAL: |
| operator.Type = Equal |
| token = scanToken(s) |
| case TokLEFTANGLEBRACKET: |
| // Can be '<', '<=', '<>'. |
| token = scanToken(s) |
| switch token.Tok { |
| case TokRIGHTANGLEBRACKET: |
| operator.Type = NotEqual |
| token = scanToken(s) |
| case TokEQUAL: |
| operator.Type = LessThanOrEqual |
| token = scanToken(s) |
| default: |
| operator.Type = LessThan |
| } |
| case TokRIGHTANGLEBRACKET: |
| // Can be '>', '>=' |
| token = scanToken(s) |
| switch token.Tok { |
| case TokEQUAL: |
| operator.Type = GreaterThanOrEqual |
| token = scanToken(s) |
| default: |
| operator.Type = GreaterThan |
| } |
| default: |
| return nil, nil, syncql.NewErrExpectedOperator(db.GetContext(), token.Off, token.Value) |
| } |
| } |
| |
| return &operator, token, nil |
| } |
| |
| // Parse logical operator and return it and the next Token (or error) |
| func parseLogicalOperator(db ds.Database, s *scanner.Scanner, token *Token) (*BinaryOperator, *Token, error) { |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| var operator BinaryOperator |
| operator.Off = token.Off |
| switch strings.ToLower(token.Value) { |
| case "and": |
| operator.Type = And |
| case "or": |
| operator.Type = Or |
| default: |
| return nil, nil, syncql.NewErrExpectedOperator(db.GetContext(), token.Off, token.Value) |
| } |
| |
| token = scanToken(s) |
| return &operator, token, nil |
| } |
| |
| // Parse and return EscapeClause, LimitClause and ResultsOffsetClause (any or all can be nil) and next token (or error) |
| func parseEscapeLimitResultsOffsetClauses(db ds.Database, s *scanner.Scanner, token *Token) (*EscapeClause, *LimitClause, *ResultsOffsetClause, *Token, error) { |
| var err error |
| var ec *EscapeClause |
| var lc *LimitClause |
| var oc *ResultsOffsetClause |
| for token.Tok != TokEOF { |
| // Note: Can be in any order. If more than one, the last one wins |
| if token.Tok == TokIDENT && strings.ToLower(token.Value) == "escape" { |
| ec, token, err = parseEscapeClause(db, s, token) |
| } else if token.Tok == TokIDENT && strings.ToLower(token.Value) == "limit" { |
| lc, token, err = parseLimitClause(db, s, token) |
| } else if token.Tok == TokIDENT && strings.ToLower(token.Value) == "offset" { |
| oc, token, err = parseResultsOffsetClause(db, s, token) |
| } else { |
| return ec, lc, oc, token, nil |
| } |
| if err != nil { |
| return nil, nil, nil, nil, err |
| } |
| } |
| return ec, lc, oc, token, nil |
| } |
| |
| // Parse and return the EscapeClause and LimitClause (any or all can be nil) and next token (or error) |
| func parseEscapeLimitClauses(db ds.Database, s *scanner.Scanner, token *Token) (*EscapeClause, *LimitClause, *Token, error) { |
| var err error |
| var ec *EscapeClause |
| var lc *LimitClause |
| for token.Tok != TokEOF { |
| // Note: Can be in any order. If more than one, the last one wins |
| if token.Tok == TokIDENT && strings.ToLower(token.Value) == "escape" { |
| ec, token, err = parseEscapeClause(db, s, token) |
| } else if token.Tok == TokIDENT && strings.ToLower(token.Value) == "limit" { |
| lc, token, err = parseLimitClause(db, s, token) |
| } else { |
| return ec, lc, token, nil |
| } |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| } |
| return ec, lc, token, nil |
| } |
| |
| // Parse the escape clause. Return the EscapeClause and the next Token (or error). |
| func parseEscapeClause(db ds.Database, s *scanner.Scanner, token *Token) (*EscapeClause, *Token, error) { |
| var ec EscapeClause |
| ec.Off = token.Off |
| token = scanToken(s) |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| if token.Tok != TokCHAR { |
| return nil, nil, syncql.NewErrExpected(db.GetContext(), token.Off, "char literal") |
| } |
| var v CharValue |
| v.Off = token.Off |
| v.Value, _ = utf8.DecodeRuneInString(token.Value) |
| ec.EscapeChar = &v |
| token = scanToken(s) |
| return &ec, token, nil |
| } |
| |
| // Parse the limit clause. Return the LimitClause and the next Token (or error). |
| func parseLimitClause(db ds.Database, s *scanner.Scanner, token *Token) (*LimitClause, *Token, error) { |
| var lc LimitClause |
| lc.Off = token.Off |
| token = scanToken(s) |
| var err error |
| lc.Limit, token, err = parseNonNegInt64(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| return &lc, token, nil |
| } |
| |
| // Parse the results offset clause. Return the ResultsOffsetClause and the next Token (or error). |
| func parseResultsOffsetClause(db ds.Database, s *scanner.Scanner, token *Token) (*ResultsOffsetClause, *Token, error) { |
| var oc ResultsOffsetClause |
| oc.Off = token.Off |
| token = scanToken(s) |
| var err error |
| oc.ResultsOffset, token, err = parseNonNegInt64(db, s, token) |
| if err != nil { |
| return nil, nil, err |
| } |
| return &oc, token, nil |
| } |
| |
| // Parse and return an Int64Value and next token (or error). |
| // This function is called by parseLimitClause and parseResultsOffsetClause. The integer |
| // values for both of these clauses cannot be negative. |
| func parseNonNegInt64(db ds.Database, s *scanner.Scanner, token *Token) (*Int64Value, *Token, error) { |
| // We expect an integer literal |
| // Since we're looking for integers >= 0, don't allow TokMINUS. |
| if token.Tok == TokEOF { |
| return nil, nil, syncql.NewErrUnexpectedEndOfStatement(db.GetContext(), token.Off) |
| } |
| if token.Tok != TokINT { |
| return nil, nil, syncql.NewErrExpected(db.GetContext(), token.Off, "positive integer literal") |
| } |
| var v Int64Value |
| v.Off = token.Off |
| var err error |
| v.Value, err = strconv.ParseInt(token.Value, 0, 64) |
| if err != nil { |
| // The token value has already been checked, so this can't happen. |
| return nil, nil, syncql.NewErrCouldNotConvert(db.GetContext(), token.Off, token.Value, "int64") |
| } |
| token = scanToken(s) |
| return &v, token, nil |
| } |
| |
| func (st SelectStatement) Offset() int64 { |
| return st.Off |
| } |
| |
| func (st DeleteStatement) Offset() int64 { |
| return st.Off |
| } |
| |
| // Pretty string of select statement. |
| func (st SelectStatement) String() string { |
| val := fmt.Sprintf("Off(%d):", st.Off) |
| if st.Select != nil { |
| val += st.Select.String() |
| } |
| if st.From != nil { |
| val += " " + st.From.String() |
| } |
| if st.Where != nil { |
| val += " " + st.Where.String() |
| } |
| if st.Escape != nil { |
| val += " " + st.Escape.String() |
| } |
| if st.Limit != nil { |
| val += " " + st.Limit.String() |
| } |
| if st.ResultsOffset != nil { |
| val += " " + st.ResultsOffset.String() |
| } |
| return val |
| } |
| |
| // Pretty string of delete statement. |
| func (st DeleteStatement) String() string { |
| val := fmt.Sprintf("Off(%d):", st.Off) |
| val += "DELETE" |
| if st.From != nil { |
| val += " " + st.From.String() |
| } |
| if st.Where != nil { |
| val += " " + st.Where.String() |
| } |
| if st.Escape != nil { |
| val += " " + st.Escape.String() |
| } |
| if st.Limit != nil { |
| val += " " + st.Limit.String() |
| } |
| return val |
| } |
| |
| func (st SelectStatement) CopyAndSubstitute(db ds.Database, paramValues []*vdl.Value) (Statement, error) { |
| var copy SelectStatement |
| copy.Off = st.Off |
| copy.Select = st.Select |
| copy.From = st.From |
| if st.Where != nil { |
| var err error |
| if copy.Where, err = st.Where.CopyAndSubstitute(db, paramValues); err != nil { |
| return nil, err |
| } |
| } else { |
| // There is no where clause. If any paramValues suppied, we have too many. |
| if len(paramValues) > 0 { |
| return nil, syncql.NewErrTooManyParamValuesSpecified(db.GetContext(), copy.Off) |
| } |
| } |
| copy.Escape = st.Escape |
| copy.Limit = st.Limit |
| copy.ResultsOffset = st.ResultsOffset |
| return copy, nil |
| } |
| |
| func (st DeleteStatement) CopyAndSubstitute(db ds.Database, paramValues []*vdl.Value) (Statement, error) { |
| var copy DeleteStatement |
| copy.Off = st.Off |
| copy.From = st.From |
| if st.Where != nil { |
| var err error |
| if copy.Where, err = st.Where.CopyAndSubstitute(db, paramValues); err != nil { |
| return nil, err |
| } |
| } else { |
| // There is no where clause. If any paramValues suppied, we have too many. |
| if len(paramValues) > 0 { |
| return nil, syncql.NewErrTooManyParamValuesSpecified(db.GetContext(), copy.Off) |
| } |
| } |
| copy.Escape = st.Escape |
| copy.Limit = st.Limit |
| return copy, nil |
| } |
| |
| func (sel SelectClause) String() string { |
| val := fmt.Sprintf(" Off(%d):SELECT Columns(", sel.Off) |
| sep := "" |
| for _, selector := range sel.Selectors { |
| val += sep + selector.String() |
| sep = "," |
| } |
| val += ")" |
| return val |
| } |
| |
| func (s Selector) String() string { |
| val := fmt.Sprintf(" Off(%d):", s.Off) |
| switch s.Type { |
| case TypSelField: |
| val += s.Field.String() |
| case TypSelFunc: |
| val += s.Function.String() |
| } |
| if s.As != nil { |
| val += s.As.String() |
| } |
| return val |
| } |
| |
| func (a AsClause) String() string { |
| val := fmt.Sprintf(" Off(%d):", a.Off) |
| val += a.AltName.String() |
| return val |
| } |
| |
| func (n Name) String() string { |
| val := fmt.Sprintf(" Off(%d):", n.Off) |
| val += n.Value |
| return val |
| } |
| |
| func (f Field) String() string { |
| val := fmt.Sprintf(" Off(%d):", f.Off) |
| for i := range f.Segments { |
| if i != 0 { |
| val += "." |
| } |
| val += f.Segments[i].String() |
| } |
| return val |
| } |
| |
| func (f Function) String() string { |
| val := fmt.Sprintf("Off(%d):", f.Off) |
| val += f.Name |
| val += "(" |
| sep := "" |
| for _, a := range f.Args { |
| val += sep + a.String() |
| sep = "," |
| } |
| val += ")" |
| return val |
| } |
| |
| func (s Segment) String() string { |
| val := fmt.Sprintf(" Off(%d):%s", s.Off, s.Value) |
| for _, k := range s.Keys { |
| val += "[" + k.String() + "]" |
| } |
| return val |
| } |
| |
| func (f FromClause) String() string { |
| return fmt.Sprintf("Off(%d):FROM %s", f.Off, f.Table.String()) |
| } |
| |
| func (t TableEntry) String() string { |
| return fmt.Sprintf("Off(%d):%s", t.Off, t.Name) |
| } |
| |
| func (w WhereClause) String() string { |
| return fmt.Sprintf(" Off(%d):WHERE %s", w.Off, w.Expr.String()) |
| } |
| |
| func (e EscapeClause) String() string { |
| return fmt.Sprintf(" Off(%d):ESCAPE %s", e.Off, e.EscapeChar.String()) |
| } |
| |
| func (i Int64Value) String() string { |
| return fmt.Sprintf(" Off(%d): %d", i.Off, i.Value) |
| } |
| |
| func (c CharValue) String() string { |
| return fmt.Sprintf(" Off(%d): %c", c.Off, c.Value) |
| } |
| |
| func (l LimitClause) String() string { |
| return fmt.Sprintf(" Off(%d):LIMIT %s", l.Off, l.Limit.String()) |
| } |
| |
| func (l ResultsOffsetClause) String() string { |
| return fmt.Sprintf(" Off(%d):OFFSET %s", l.Off, l.ResultsOffset.String()) |
| } |
| |
| func (o Operand) String() string { |
| val := fmt.Sprintf("Off(%d):", o.Off) |
| switch o.Type { |
| case TypBigInt: |
| val += "(BigInt)" |
| val += o.BigInt.String() |
| case TypBigRat: |
| val += "(BigRat)" |
| val += o.BigRat.String() |
| case TypComplex: |
| val += "(Complex)" |
| val += fmt.Sprintf("%g", o.Complex) |
| case TypField: |
| val += "(field)" |
| val += o.Column.String() |
| case TypBool: |
| val += "(bool)" |
| val += strconv.FormatBool(o.Bool) |
| case TypInt: |
| val += "(int)" |
| val += strconv.FormatInt(o.Int, 10) |
| case TypFloat: |
| val += "(float)" |
| val += strconv.FormatFloat(o.Float, 'f', -1, 64) |
| case TypFunction: |
| val += "(function)" |
| val += o.Function.String() |
| case TypStr: |
| val += "(string)" |
| val += o.Str |
| case TypExpr: |
| val += "(expr)" |
| val += o.Expr.String() |
| case TypTime: |
| val += "(time)" |
| val += o.Time.Format("Mon Jan 2 15:04:05 -0700 MST 2006") |
| case TypNil: |
| val += "<nil>" |
| case TypObject: |
| val += "(object)" |
| val += fmt.Sprintf("%v", o.Object) |
| case TypParameter: |
| val += "?" |
| default: |
| val += "<operand-type-undefined>" |
| |
| } |
| return val |
| } |
| |
| func (o BinaryOperator) String() string { |
| val := fmt.Sprintf("Off(%d):", o.Off) |
| switch o.Type { |
| case And: |
| val += "AND" |
| case Equal: |
| val += "=" |
| case GreaterThan: |
| val += ">" |
| case GreaterThanOrEqual: |
| val += ">=" |
| case Is: |
| val += "IS" |
| case IsNot: |
| val += "IS NOT" |
| case LessThan: |
| val += "<" |
| case LessThanOrEqual: |
| val += "<=" |
| case Like: |
| val += "LIKE" |
| case NotEqual: |
| val += "<>" |
| case NotLike: |
| val += "NOT LIKE" |
| case Or: |
| val += "OR" |
| default: |
| val += "<operator-undefined>" |
| } |
| return val |
| } |
| |
| func (e Expression) String() string { |
| return fmt.Sprintf("(Off(%d):%s %s %s)", e.Off, e.Operand1.String(), e.Operator.String(), e.Operand2.String()) |
| } |
| |
| // paramInfo is used to keep track of supplied values for parameter markers. |
| type paramInfo struct { |
| paramValues []*vdl.Value |
| cursor int // Index into paramValues pointing to next value to substitute. |
| } |
| |
| func (w WhereClause) CopyAndSubstitute(db ds.Database, paramValues []*vdl.Value) (*WhereClause, error) { |
| var copy WhereClause |
| copy.Off = w.Off |
| var err error |
| pi := paramInfo{paramValues: paramValues, cursor: 0} |
| |
| if copy.Expr, err = w.Expr.CopyAndSubstitute(db, &pi); err != nil { |
| return nil, err |
| } |
| |
| // Did any of the supplied values go unused? |
| if pi.cursor < len(paramValues) { |
| return nil, syncql.NewErrTooManyParamValuesSpecified(db.GetContext(), w.Off) |
| } |
| |
| return ©, nil |
| } |
| |
| func (e Expression) CopyAndSubstitute(db ds.Database, pi *paramInfo) (*Expression, error) { |
| var copy Expression |
| copy.Off = e.Off |
| copy.Operator = e.Operator |
| var err error |
| if copy.Operand1, err = e.Operand1.CopyAndSubstitute(db, pi); err != nil { |
| return nil, err |
| } |
| if copy.Operand2, err = e.Operand2.CopyAndSubstitute(db, pi); err != nil { |
| return nil, err |
| } |
| return ©, nil |
| } |
| |
| func (o Operand) CopyAndSubstitute(db ds.Database, pi *paramInfo) (*Operand, error) { |
| switch o.Type { |
| case TypExpr: |
| var copy Operand |
| copy.Type = TypExpr |
| copy.Off = o.Off |
| var err error |
| if copy.Expr, err = o.Expr.CopyAndSubstitute(db, pi); err != nil { |
| return nil, err |
| } |
| return ©, nil |
| case TypFunction: |
| var copy Operand |
| copy.Type = TypFunction |
| copy.Off = o.Off |
| var err error |
| if copy.Function, err = o.Function.CopyAndSubstitute(db, pi); err != nil { |
| return nil, err |
| } |
| return ©, nil |
| case TypParameter: |
| if pi.cursor >= len(pi.paramValues) { |
| // not enough paramater values specified |
| return nil, syncql.NewErrNotEnoughParamValuesSpecified(db.GetContext(), o.Off) |
| } |
| if cpOp, err := ConvertValueToAnOperand(pi.paramValues[pi.cursor], o.Off); err == nil { |
| pi.cursor++ |
| return cpOp, nil |
| } else { |
| return nil, err |
| } |
| default: |
| // No need to copy the operand. |
| return &o, nil |
| } |
| } |
| |
| func (f Function) CopyAndSubstitute(db ds.Database, pi *paramInfo) (*Function, error) { |
| var copy Function |
| copy.Name = f.Name |
| copy.Off = f.Off |
| |
| for _, a := range f.Args { |
| if newArg, err := a.CopyAndSubstitute(db, pi); err == nil { |
| copy.Args = append(copy.Args, newArg) |
| } else { |
| return nil, err |
| } |
| } |
| |
| copy.RetType = f.RetType |
| copy.Computed = f.Computed |
| if copy.Computed { |
| var err error |
| if copy.RetValue, err = f.RetValue.CopyAndSubstitute(db, pi); err != nil { |
| return nil, err |
| } |
| } |
| return ©, nil |
| } |
| |
| func ConvertValueToAnOperand(value *vdl.Value, off int64) (*Operand, error) { |
| var op Operand |
| op.Off = off |
| |
| switch value.Kind() { |
| case vdl.Bool: |
| op.Type = TypBool |
| op.Bool = value.Bool() |
| case vdl.Enum: |
| op.Type = TypStr |
| op.Str = value.EnumLabel() |
| case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64: |
| op.Type = TypInt |
| op.Int = value.Int() |
| case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64: |
| op.Type = TypInt |
| op.Int = int64(value.Uint()) |
| case vdl.Float32, vdl.Float64: |
| op.Type = TypFloat |
| op.Float = value.Float() |
| case vdl.String: |
| op.Type = TypStr |
| op.Str = value.RawString() |
| case vdl.Complex64, vdl.Complex128: |
| op.Type = TypComplex |
| op.Complex = value.Complex() |
| default: // OpObject for structs, arrays, maps, ... |
| if value.Kind() == vdl.Struct && value.Type().Name() == "time.Time" { |
| op.Type = TypTime |
| if err := vdl.Convert(&op.Time, value); err != nil { |
| return nil, err |
| } |
| } else { |
| op.Type = TypObject |
| op.Object = value |
| } |
| } |
| return &op, nil |
| } |
| |
| // ParseIndexField is used to parse datasource supplied index fields. It creates a new |
| // scanner with the contents of the field name and only succeeds if the result of |
| // parsing the contents of the scan is a field and there is nothing left over. |
| // Note: This function is NOT involved in the parsing of the AST. Offsets of 0 are |
| // returned on error as these errors are unrelated to the input query. They |
| // are configuration errors in the datasource. |
| func ParseIndexField(db ds.Database, fieldName, tableName string) (*Field, error) { |
| // Set up a scanner and call the parser's parseOperand function. |
| r := strings.NewReader(fieldName) |
| var s scanner.Scanner |
| s.Init(r) |
| s.Error = scannerError |
| |
| token := scanToken(&s) |
| if token.Tok == TokEOF { |
| return nil, syncql.NewErrInvalidIndexField(db.GetContext(), 0, fieldName, tableName) |
| } |
| var op *Operand |
| var err error |
| op, token, err = parseOperand(db, &s, token) |
| if err != nil { |
| return nil, syncql.NewErrInvalidIndexField(db.GetContext(), 0, fieldName, tableName) |
| } |
| if op.Type != TypField { |
| return nil, syncql.NewErrInvalidIndexField(db.GetContext(), 0, fieldName, tableName) |
| } |
| if token.Tok != TokEOF { |
| return nil, syncql.NewErrInvalidIndexField(db.GetContext(), 0, fieldName, tableName) |
| } |
| // Look at last segment. If a key or index is supplied, it can't be used as an index. |
| if len(op.Column.Segments[len(op.Column.Segments)-1].Keys) != 0 { |
| return nil, syncql.NewErrInvalidIndexField(db.GetContext(), 0, fieldName, tableName) |
| } |
| |
| return op.Column, nil |
| } |