• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codenotary / immudb / 24841571249

23 Apr 2026 02:44PM UTC coverage: 85.275% (-4.0%) from 89.306%
24841571249

push

gh-ci

web-flow
feat: v1.11.0 PostgreSQL compatibility and SQL feature expansion (#2090)

* Add structured audit logging with immutable audit trail

Introduces a new --audit-log flag that records all gRPC operations as
structured JSON events in immudb's tamper-proof KV store. Events are
stored under the audit: key prefix in systemdb, queryable via Scan and
verifiable via VerifiableGet. An async buffered writer ensures minimal
latency impact. Configurable event filtering (all/write/admin) via
--audit-log-events flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add PostgreSQL ORM compatibility layer and verification functions

Extend the pgsql wire protocol with immudb verification functions
(immudb_state, immudb_verify_row, immudb_verify_tx, immudb_history,
immudb_tx) accessible via standard SQL SELECT statements.

Add pg_catalog resolvers (pg_attribute, pg_index, pg_constraint,
pg_type, pg_settings, pg_description) and information_schema
resolvers (tables, columns, schemata, key_column_usage) to support
ORM introspection from Django, SQLAlchemy, GORM, and ActiveRecord.

Add PostgreSQL compatibility functions: current_database,
current_schema, current_user, format_type, pg_encoding_to_char,
pg_get_expr, pg_get_constraintdef, obj_description, col_description,
has_table_privilege, has_schema_privilege, and others.

Add SHOW statement emulation for common ORM config queries and
schema-qualified name stripping for information_schema and public
schema references.

* Implement EXISTS and IN subquery support in SQL engine

Replace the previously stubbed ExistsBoolExp and InSubQueryExp
implementations with working non-correlated subquery execution.

EXISTS subqueries resolve the inner SELECT and check if any rows
are returned. IN subqueries resolve the inner SELECT, iterate the
result set, and compare each value against the outer expression.
Both support NOT variants (NOT EXISTS, NOT IN).

Correlated subqueries (referencing outer query columns) ar... (continued)

7254 of 10471 new or added lines in 124 files covered. (69.28%)

119 existing lines in 18 files now uncovered.

44597 of 52298 relevant lines covered (85.27%)

127591.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.96
/embedded/sql/parser.go
1
/*
2
Copyright 2026 Codenotary Inc. All rights reserved.
3

4
SPDX-License-Identifier: BUSL-1.1
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    https://mariadb.com/bsl11/
9

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

17
package sql
18

19
import (
20
        "bytes"
21
        "encoding/hex"
22
        "errors"
23
        "fmt"
24
        "io"
25
        "strconv"
26
        "strings"
27
)
28

29
//go:generate go run golang.org/x/tools/cmd/goyacc -l -o sql_parser.go sql_grammar.y
30

31
var keywords = map[string]int{
32
        "CREATE":         CREATE,
33
        "DROP":           DROP,
34
        "TRUNCATE":       TRUNCATE,
35
        "USE":            USE,
36
        "DATABASE":       DATABASE,
37
        "SNAPSHOT":       SNAPSHOT,
38
        "HISTORY":        HISTORY,
39
        "DIFF":           DIFF,
40
        "OF":             OF,
41
        "SINCE":          SINCE,
42
        "AFTER":          AFTER,
43
        "BEFORE":         BEFORE,
44
        "UNTIL":          UNTIL,
45
        "TABLE":          TABLE,
46
        "PRIMARY":        PRIMARY,
47
        "KEY":            KEY,
48
        "UNIQUE":         UNIQUE,
49
        "INDEX":          INDEX,
50
        "ON":             ON,
51
        "ALTER":          ALTER,
52
        "ADD":            ADD,
53
        "RENAME":         RENAME,
54
        "TO":             TO,
55
        "COLUMN":         COLUMN,
56
        "INSERT":         INSERT,
57
        "CONFLICT":       CONFLICT,
58
        "DO":             DO,
59
        "NOTHING":        NOTHING,
60
        "RETURNING":      RETURNING,
61
        "UPSERT":         UPSERT,
62
        "INTO":           INTO,
63
        "VALUES":         VALUES,
64
        "UPDATE":         UPDATE,
65
        "SET":            SET,
66
        "DELETE":         DELETE,
67
        "BEGIN":          BEGIN,
68
        "TRANSACTION":    TRANSACTION,
69
        "COMMIT":         COMMIT,
70
        "ROLLBACK":       ROLLBACK,
71
        "SAVEPOINT":      SAVEPOINT,
72
        "RELEASE":        RELEASE,
73
        "LATERAL":        LATERAL,
74
        "SELECT":         SELECT,
75
        "DISTINCT":       DISTINCT,
76
        "FROM":           FROM,
77
        "UNION":          UNION,
78
        "ALL":            ALL,
79
        "EXCEPT":         EXCEPT,
80
        "INTERSECT":      INTERSECT,
81
        "NULLS":          NULLS,
82
        "FIRST":          FIRST,
83
        "LAST":           LAST,
84
        "VIEW":           VIEW,
85
        "OVER":           OVER,
86
        "PARTITION":      PARTITION,
87
        "EXPLAIN":        EXPLAIN,
88
        "RECURSIVE":      RECURSIVE,
89
        "NATURAL":        NATURAL,
90
        "USING":          USING,
91
        "ILIKE":          ILIKE,
92
        "DEFAULT":        DEFAULT,
93
        "FETCH":          FETCH,
94
        "ROWS":           ROWS,
95
        "ONLY":           ONLY,
96
        "FOREIGN":        FOREIGN,
97
        "REFERENCES":     REFERENCES,
98
        "CASCADE":        CASCADE,
99
        "SEQUENCE":       SEQUENCE,
100
        "TX":             TX,
101
        "JOIN":           JOIN,
102
        "HAVING":         HAVING,
103
        "WHERE":          WHERE,
104
        "GROUP":          GROUP,
105
        "BY":             BY,
106
        "LIMIT":          LIMIT,
107
        "OFFSET":         OFFSET,
108
        "ORDER":          ORDER,
109
        "AS":             AS,
110
        "ASC":            ASC,
111
        "DESC":           DESC,
112
        "AND":            AND,
113
        "OR":             OR,
114
        "NOT":            NOT,
115
        "LIKE":           LIKE,
116
        "EXISTS":         EXISTS,
117
        "BETWEEN":        BETWEEN,
118
        "IN":             IN,
119
        "AUTO_INCREMENT": AUTO_INCREMENT,
120
        "NULL":           NULL,
121
        "IF":             IF,
122
        "IS":             IS,
123
        "CAST":           CAST,
124
        "::":             SCAST,
125
        "SHOW":           SHOW,
126
        "DATABASES":      DATABASES,
127
        "TABLES":         TABLES,
128
        "USERS":          USERS,
129
        "USER":           USER,
130
        "WITH":           WITH,
131
        "PASSWORD":       PASSWORD,
132
        "READ":           READ,
133
        "READWRITE":      READWRITE,
134
        "ADMIN":          ADMIN,
135
        "GRANT":          GRANT,
136
        "REVOKE":         REVOKE,
137
        "GRANTS":         GRANTS,
138
        "FOR":            FOR,
139
        "PRIVILEGES":     PRIVILEGES,
140
        "CHECK":          CHECK,
141
        "CONSTRAINT":     CONSTRAINT,
142
        "CASE":           CASE,
143
        "WHEN":           WHEN,
144
        "THEN":           THEN,
145
        "ELSE":           ELSE,
146
        "END":            END,
147
        "EXTRACT":        EXTRACT,
148
        "INTEGER":        INTEGER_TYPE,
149
        "INT":            INTEGER_TYPE,
150
        "INT4":           INTEGER_TYPE,
151
        "INT8":           INTEGER_TYPE,
152
        "BIGINT":         INTEGER_TYPE,
153
        "SMALLINT":       INTEGER_TYPE,
154
        "SERIAL":         INTEGER_TYPE,
155
        "BIGSERIAL":      INTEGER_TYPE,
156
        "BOOLEAN":        BOOLEAN_TYPE,
157
        "VARCHAR":        VARCHAR_TYPE,
158
        "TIMESTAMP":      TIMESTAMP_TYPE,
159
        "TIMESTAMPTZ":    TIMESTAMP_TYPE,
160
        "DATE":           TIMESTAMP_TYPE,
161
        "FLOAT":          FLOAT_TYPE,
162
        "FLOAT4":         FLOAT_TYPE,
163
        "FLOAT8":         FLOAT_TYPE,
164
        "DOUBLE":         FLOAT_TYPE,
165
        "REAL":           FLOAT_TYPE,
166
        "NUMERIC":        FLOAT_TYPE,
167
        "DECIMAL":        FLOAT_TYPE,
168
        "BLOB":           BLOB_TYPE,
169
        "BYTEA":          BLOB_TYPE,
170
        "UUID":           UUID_TYPE,
171
        "JSON":           JSON_TYPE,
172
        "JSONB":          JSON_TYPE,
173
        "YEAR":           YEAR,
174
        "MONTH":          MONTH,
175
        "DAY":            DAY,
176
        "HOUR":           HOUR,
177
        "MINUTE":         MINUTE,
178
        "SECOND":         SECOND,
179
}
180

181
var joinTypes = map[string]JoinType{
182
        "INNER": InnerJoin,
183
        "LEFT":  LeftJoin,
184
        "RIGHT": RightJoin,
185
        "CROSS": CrossJoin,
186
        "FULL":  FullOuterJoin,
187
}
188

189
var aggregateFns = map[string]AggregateFn{
190
        "COUNT":      COUNT,
191
        "SUM":        SUM,
192
        "MAX":        MAX,
193
        "MIN":        MIN,
194
        "AVG":        AVG,
195
        "STRING_AGG": STRING_AGG,
196
}
197

198
var boolValues = map[string]bool{
199
        "TRUE":  true,
200
        "FALSE": false,
201
}
202

203
var cmpOps = map[string]CmpOperator{
204
        "=":  EQ,
205
        "!=": NE,
206
        "<>": NE,
207
        "<":  LT,
208
        "<=": LE,
209
        ">":  GT,
210
        ">=": GE,
211
}
212

213
var ErrEitherNamedOrUnnamedParams = errors.New("either named or unnamed params")
214
var ErrEitherPosOrNonPosParams = errors.New("either positional or non-positional named params")
215
var ErrInvalidPositionalParameter = errors.New("invalid positional parameter")
216

217
type positionalParamType int
218

219
const (
220
        NamedNonPositionalParamType positionalParamType = iota + 1
221
        NamedPositionalParamType
222
        UnnamedParamType
223
)
224

225
type lexer struct {
226
        r               *aheadByteReader
227
        err             error
228
        namedParamsType positionalParamType
229
        paramsCount     int
230
        result          []SQLStmt
231
}
232

233
type aheadByteReader struct {
234
        nextChar  byte
235
        nextErr   error
236
        r         io.ByteReader
237
        readCount int
238
}
239

240
func newAheadByteReader(r io.ByteReader) *aheadByteReader {
8,074✔
241
        ar := &aheadByteReader{r: r}
8,074✔
242
        ar.nextChar, ar.nextErr = r.ReadByte()
8,074✔
243
        return ar
8,074✔
244
}
8,074✔
245

246
func (ar *aheadByteReader) ReadByte() (byte, error) {
543,476✔
247
        defer func() {
1,086,952✔
248
                if ar.nextErr == nil {
1,079,052✔
249
                        ar.nextChar, ar.nextErr = ar.r.ReadByte()
535,576✔
250
                }
535,576✔
251
        }()
252

253
        ar.readCount++
543,476✔
254

543,476✔
255
        return ar.nextChar, ar.nextErr
543,476✔
256
}
257

258
func (ar *aheadByteReader) ReadCount() int {
151✔
259
        return ar.readCount
151✔
260
}
151✔
261

262
func (ar *aheadByteReader) NextByte() (byte, error) {
412,903✔
263
        return ar.nextChar, ar.nextErr
412,903✔
264
}
412,903✔
265

266
func ParseSQLString(sql string) ([]SQLStmt, error) {
1,100✔
267
        return ParseSQL(strings.NewReader(sql))
1,100✔
268
}
1,100✔
269

270
func ParseSQL(r io.ByteReader) ([]SQLStmt, error) {
8,074✔
271
        lexer := newLexer(r)
8,074✔
272

8,074✔
273
        yyParse(lexer)
8,074✔
274

8,074✔
275
        return lexer.result, lexer.err
8,074✔
276
}
8,074✔
277

278
func ParseExpFromString(exp string) (ValueExp, error) {
146✔
279
        stmt := fmt.Sprintf("SELECT * FROM t WHERE %s", exp)
146✔
280

146✔
281
        res, err := ParseSQLString(stmt)
146✔
282
        if err != nil {
146✔
283
                return nil, err
×
284
        }
×
285

286
        s := res[0].(*SelectStmt)
146✔
287
        return s.where, nil
146✔
288
}
289

290
func newLexer(r io.ByteReader) *lexer {
8,074✔
291
        return &lexer{
8,074✔
292
                r:   newAheadByteReader(r),
8,074✔
293
                err: nil,
8,074✔
294
        }
8,074✔
295
}
8,074✔
296

297
func (l *lexer) Lex(lval *yySymType) int {
139,051✔
298
        var ch byte
139,051✔
299
        var err error
139,051✔
300

139,051✔
301
        for {
350,806✔
302
                ch, err = l.r.ReadByte()
211,755✔
303
                if err == io.EOF {
219,651✔
304
                        return 0
7,896✔
305
                }
7,896✔
306
                if err != nil {
203,859✔
307
                        lval.err = err
×
308
                        return ERROR
×
309
                }
×
310

311
                if ch == '\t' {
209,856✔
312
                        continue
5,997✔
313
                }
314

315
                if ch == '/' && l.r.nextChar == '*' {
197,869✔
316
                        l.r.ReadByte()
7✔
317

7✔
318
                        for {
129✔
319
                                ch, err := l.r.ReadByte()
122✔
320
                                if err == io.EOF {
122✔
321
                                        break
×
322
                                }
323
                                if err != nil {
122✔
324
                                        lval.err = err
×
325
                                        return ERROR
×
326
                                }
×
327

328
                                if ch == '*' && l.r.nextChar == '/' {
129✔
329
                                        l.r.ReadByte() // consume closing slash
7✔
330
                                        break
7✔
331
                                }
332
                        }
333

334
                        continue
7✔
335
                }
336

337
                if ch == '-' && l.r.nextChar == '-' {
197,871✔
338
                        l.r.ReadByte() // consume second dash
16✔
339

16✔
340
                        for {
142✔
341
                                ch, err := l.r.ReadByte()
126✔
342
                                if err == io.EOF || isLineBreak(ch) {
142✔
343
                                        break
16✔
344
                                }
345
                                if err != nil {
110✔
NEW
346
                                        lval.err = err
×
NEW
347
                                        return ERROR
×
NEW
348
                                }
×
349
                        }
350

351
                        continue
16✔
352
                }
353

354
                if isLineBreak(ch) {
199,891✔
355
                        if ch == '\r' && l.r.nextChar == '\n' {
2,053✔
356
                                l.r.ReadByte()
1✔
357
                        }
1✔
358
                        continue
2,052✔
359
                }
360

361
                if !isSpace(ch) {
326,942✔
362
                        break
131,155✔
363
                }
364
        }
365

366
        if isSeparator(ch) {
133,201✔
367
                return STMT_SEPARATOR
2,046✔
368
        }
2,046✔
369

370
        if ch == '-' && l.r.nextChar == '>' {
129,158✔
371
                l.r.ReadByte()
49✔
372
                return ARROW
49✔
373
        }
49✔
374

375
        if isBLOBPrefix(ch) && isQuote(l.r.nextChar) {
129,155✔
376
                l.r.ReadByte() // consume starting quote
95✔
377

95✔
378
                tail, err := l.readString()
95✔
379
                if err != nil {
95✔
380
                        lval.err = err
×
381
                        return ERROR
×
382
                }
×
383

384
                val, err := hex.DecodeString(tail)
95✔
385
                if err != nil {
95✔
386
                        lval.err = err
×
387
                        return ERROR
×
388
                }
×
389

390
                lval.blob = val
95✔
391
                return BLOB_LIT
95✔
392
        }
393

394
        if isLetter(ch) {
189,241✔
395
                tail, err := l.readWord()
60,276✔
396
                if err != nil {
60,276✔
397
                        lval.err = err
×
398
                        return ERROR
×
399
                }
×
400

401
                w := fmt.Sprintf("%c%s", ch, tail)
60,276✔
402
                tid := strings.ToUpper(w)
60,276✔
403

60,276✔
404
                val, ok := boolValues[tid]
60,276✔
405
                if ok {
60,538✔
406
                        lval.boolean = val
262✔
407
                        return BOOLEAN_LIT
262✔
408
                }
262✔
409

410
                afn, ok := aggregateFns[tid]
60,014✔
411
                if ok {
60,229✔
412
                        lval.aggFn = afn
215✔
413
                        return AGGREGATE_FUNC
215✔
414
                }
215✔
415

416
                join, ok := joinTypes[tid]
59,799✔
417
                if ok {
59,853✔
418
                        lval.joinType = join
54✔
419
                        return JOINTYPE
54✔
420
                }
54✔
421

422
                tkn, ok := keywords[tid]
59,745✔
423
                if ok {
90,443✔
424
                        lval.keyword = w
30,698✔
425
                        return tkn
30,698✔
426
                }
30,698✔
427

428
                lval.id = strings.ToLower(w)
29,047✔
429
                return IDENTIFIER
29,047✔
430
        }
431

432
        if isDoubleQuote(ch) {
68,696✔
433
                tail, err := l.readWord()
7✔
434
                if err != nil {
7✔
435
                        lval.err = err
×
436
                        return ERROR
×
437
                }
×
438

439
                if !isDoubleQuote(l.r.nextChar) {
8✔
440
                        lval.err = fmt.Errorf("double quote expected")
1✔
441
                        return ERROR
1✔
442
                }
1✔
443

444
                l.r.ReadByte() // consume ending quote
6✔
445

6✔
446
                lval.id = strings.ToLower(tail)
6✔
447
                return IDENTIFIER
6✔
448
        }
449

450
        if isNumber(ch) {
74,385✔
451
                tail, err := l.readNumber()
5,703✔
452
                if err != nil {
5,703✔
453
                        lval.err = err
×
454
                        return ERROR
×
455
                }
×
456
                // looking for a float
457
                if isDot(l.r.nextChar) {
5,780✔
458
                        l.r.ReadByte() // consume dot
77✔
459

77✔
460
                        decimalPart, err := l.readNumber()
77✔
461
                        if err != nil {
77✔
462
                                lval.err = err
×
463
                                return ERROR
×
464
                        }
×
465

466
                        val, err := strconv.ParseFloat(fmt.Sprintf("%c%s.%s", ch, tail, decimalPart), 64)
77✔
467
                        if err != nil {
78✔
468
                                lval.err = err
1✔
469
                                return ERROR
1✔
470
                        }
1✔
471

472
                        lval.float = val
76✔
473
                        return FLOAT_LIT
76✔
474
                }
475

476
                val, err := strconv.ParseUint(fmt.Sprintf("%c%s", ch, tail), 10, 64)
5,626✔
477
                if err != nil {
5,626✔
478
                        lval.err = err
×
479
                        return ERROR
×
480
                }
×
481

482
                lval.integer = val
5,626✔
483
                return INTEGER_LIT
5,626✔
484
        }
485

486
        if isComparison(ch) {
63,666✔
487
                tail, err := l.readComparison()
687✔
488
                if err != nil {
687✔
489
                        lval.err = err
×
490
                        return ERROR
×
491
                }
×
492

493
                op := fmt.Sprintf("%c%s", ch, tail)
687✔
494
                if op == "!~" {
688✔
495
                        return NOT_MATCHES_OP
1✔
496
                }
1✔
497

498
                cmpOp, ok := cmpOps[op]
686✔
499
                if !ok {
686✔
500
                        lval.err = fmt.Errorf("invalid comparison operator %s", op)
×
501
                        return ERROR
×
502
                }
×
503

504
                lval.cmpOp = cmpOp
686✔
505
                return CMPOP
686✔
506
        }
507

508
        if isQuote(ch) {
66,269✔
509
                tail, err := l.readString()
3,977✔
510
                if err != nil {
3,977✔
511
                        lval.err = err
×
512
                        return ERROR
×
513
                }
×
514

515
                lval.str = tail
3,977✔
516
                return VARCHAR_LIT
3,977✔
517
        }
518

519
        if ch == ':' {
58,344✔
520
                ch, err := l.r.ReadByte()
29✔
521
                if err != nil {
29✔
522
                        lval.err = err
×
523
                        return ERROR
×
524
                }
×
525

526
                if ch != ':' {
29✔
527
                        lval.err = fmt.Errorf("colon expected")
×
528
                        return ERROR
×
529
                }
×
530

531
                return SCAST
29✔
532
        }
533

534
        if ch == '@' {
65,723✔
535
                if l.namedParamsType == UnnamedParamType {
7,438✔
536
                        lval.err = ErrEitherNamedOrUnnamedParams
1✔
537
                        return ERROR
1✔
538
                }
1✔
539

540
                if l.namedParamsType == NamedPositionalParamType {
7,437✔
541
                        lval.err = ErrEitherPosOrNonPosParams
1✔
542
                        return ERROR
1✔
543
                }
1✔
544

545
                l.namedParamsType = NamedNonPositionalParamType
7,435✔
546

7,435✔
547
                ch, err := l.r.NextByte()
7,435✔
548
                if err != nil {
7,435✔
549
                        lval.err = err
×
550
                        return ERROR
×
551
                }
×
552

553
                if !isLetter(ch) {
7,435✔
554
                        return ERROR
×
555
                }
×
556

557
                id, err := l.readWord()
7,435✔
558
                if err != nil {
7,435✔
559
                        lval.err = err
×
560
                        return ERROR
×
561
                }
×
562

563
                lval.id = strings.ToLower(id)
7,435✔
564

7,435✔
565
                return NPARAM
7,435✔
566
        }
567

568
        if ch == '$' {
50,945✔
569
                if l.namedParamsType == UnnamedParamType {
97✔
570
                        lval.err = ErrEitherNamedOrUnnamedParams
1✔
571
                        return ERROR
1✔
572
                }
1✔
573

574
                if l.namedParamsType == NamedNonPositionalParamType {
96✔
575
                        lval.err = ErrEitherPosOrNonPosParams
1✔
576
                        return ERROR
1✔
577
                }
1✔
578

579
                id, err := l.readNumber()
94✔
580
                if err != nil {
94✔
581
                        lval.err = err
×
582
                        return ERROR
×
583
                }
×
584

585
                pid, err := strconv.Atoi(id)
94✔
586
                if err != nil {
95✔
587
                        lval.err = err
1✔
588
                        return ERROR
1✔
589
                }
1✔
590

591
                if pid < 1 {
94✔
592
                        lval.err = ErrInvalidPositionalParameter
1✔
593
                        return ERROR
1✔
594
                }
1✔
595

596
                lval.pparam = pid
92✔
597

92✔
598
                l.namedParamsType = NamedPositionalParamType
92✔
599

92✔
600
                return PPARAM
92✔
601
        }
602

603
        if ch == '?' {
50,886✔
604
                if l.namedParamsType == NamedNonPositionalParamType || l.namedParamsType == NamedPositionalParamType {
135✔
605
                        lval.err = ErrEitherNamedOrUnnamedParams
2✔
606
                        return ERROR
2✔
607
                }
2✔
608

609
                l.paramsCount++
131✔
610
                lval.pparam = l.paramsCount
131✔
611

131✔
612
                l.namedParamsType = UnnamedParamType
131✔
613

131✔
614
                return PPARAM
131✔
615
        }
616

617
        if isDot(ch) {
51,170✔
618
                if isNumber(l.r.nextChar) { // looking for  a float
555✔
619
                        decimalPart, err := l.readNumber()
5✔
620
                        if err != nil {
5✔
621
                                lval.err = err
×
622
                                return ERROR
×
623
                        }
×
624
                        val, err := strconv.ParseFloat(fmt.Sprintf("%d.%s", 0, decimalPart), 64)
5✔
625
                        if err != nil {
5✔
626
                                lval.err = err
×
627
                                return ERROR
×
628
                        }
×
629
                        lval.float = val
5✔
630
                        return FLOAT_LIT
5✔
631
                }
632
                return DOT
545✔
633
        }
634

635
        return int(ch)
50,070✔
636
}
637

638
// tokenFriendlyNames rewrites goyacc's raw grammar-token names into
639
// descriptions a human who has never read sql_grammar.y will recognise.
640
// Applies to yyerror output such as
641
//
642
//        "syntax error: unexpected VARCHAR_LIT, expecting ')'"
643
//
644
// which becomes "... unexpected string literal, expecting ')'".
645
// Only uppercase-with-underscore names appear in goyacc's verbose error
646
// format, so there is no risk of matching inside an identifier.
647
var tokenFriendlyNames = strings.NewReplacer(
648
        "VARCHAR_LIT", "string literal",
649
        "INTEGER_LIT", "integer",
650
        "FLOAT_LIT", "number",
651
        "BLOB_LIT", "binary literal",
652
        "BOOLEAN_LIT", "boolean",
653
        "NPARAM", "named parameter",
654
        "PPARAM", "positional parameter",
655
        "VARCHAR_TYPE", "VARCHAR",
656
        "INTEGER_TYPE", "INTEGER",
657
        "BOOLEAN_TYPE", "BOOLEAN",
658
        "BLOB_TYPE", "BLOB",
659
        "FLOAT_TYPE", "FLOAT",
660
        "TIMESTAMP_TYPE", "TIMESTAMP",
661
        "UUID_TYPE", "UUID",
662
        "JSON_TYPE", "JSON",
663
        "AGGREGATE_FUNC", "aggregate function",
664
        "STMT_SEPARATOR", "';'",
665
)
666

667
func (l *lexer) Error(err string) {
151✔
668
        l.err = fmt.Errorf("%s at position %d", tokenFriendlyNames.Replace(err), l.r.ReadCount())
151✔
669
}
151✔
670

671
func (l *lexer) readWord() (string, error) {
67,718✔
672
        return l.readWhile(func(ch byte) bool {
409,834✔
673
                return isLetter(ch) || isNumber(ch)
342,116✔
674
        })
342,116✔
675
}
676

677
func (l *lexer) readNumber() (string, error) {
5,879✔
678
        return l.readWhile(isNumber)
5,879✔
679
}
5,879✔
680

681
func (l *lexer) readString() (string, error) {
4,072✔
682
        var b bytes.Buffer
4,072✔
683

4,072✔
684
        for {
42,174✔
685
                ch, err := l.r.ReadByte()
38,102✔
686
                if err != nil {
38,102✔
687
                        return "", err
×
688
                }
×
689

690
                nextCh, _ := l.r.NextByte()
38,102✔
691

38,102✔
692
                if isQuote(ch) {
42,176✔
693
                        if isQuote(nextCh) {
4,076✔
694
                                l.r.ReadByte() // consume escaped quote
2✔
695
                        } else {
4,074✔
696
                                break // string completely read
4,072✔
697
                        }
698
                }
699

700
                b.WriteByte(ch)
34,030✔
701
        }
702

703
        return b.String(), nil
4,072✔
704
}
705

706
func (l *lexer) readComparison() (string, error) {
687✔
707
        return l.readWhile(func(ch byte) bool {
1,477✔
708
                return isComparison(ch)
790✔
709
        })
790✔
710
}
711

712
func (l *lexer) readWhile(condFn func(b byte) bool) (string, error) {
74,284✔
713
        var b bytes.Buffer
74,284✔
714

74,284✔
715
        for {
441,650✔
716
                ch, err := l.r.NextByte()
367,366✔
717
                if err == io.EOF {
368,625✔
718
                        break
1,259✔
719
                }
720
                if err != nil {
366,107✔
721
                        return "", err
×
722
                }
×
723

724
                if !condFn(ch) {
439,132✔
725
                        break
73,025✔
726
                }
727

728
                ch, _ = l.r.ReadByte()
293,082✔
729
                b.WriteByte(ch)
293,082✔
730
        }
731

732
        return b.String(), nil
74,284✔
733
}
734

735
func isBLOBPrefix(ch byte) bool {
129,060✔
736
        return ch == 'x'
129,060✔
737
}
129,060✔
738

739
func isSeparator(ch byte) bool {
131,155✔
740
        return ch == ';'
131,155✔
741
}
131,155✔
742

743
func isLineBreak(ch byte) bool {
197,961✔
744
        return ch == '\r' || ch == '\n'
197,961✔
745
}
197,961✔
746

747
func isSpace(ch byte) bool {
195,787✔
748
        return ch == 32 || ch == 9 //SPACE or TAB
195,787✔
749
}
195,787✔
750

751
func isNumber(ch byte) bool {
162,420✔
752
        return '0' <= ch && ch <= '9'
162,420✔
753
}
162,420✔
754

755
func isLetter(ch byte) bool {
478,516✔
756
        return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
478,516✔
757
}
478,516✔
758

759
func isComparison(ch byte) bool {
63,769✔
760
        return ch == '!' || ch == '<' || ch == '=' || ch == '>' || ch == '~'
63,769✔
761
}
63,769✔
762

763
func isQuote(ch byte) bool {
104,571✔
764
        return ch == 0x27
104,571✔
765
}
104,571✔
766

767
func isDoubleQuote(ch byte) bool {
68,696✔
768
        return ch == 0x22
68,696✔
769
}
68,696✔
770

771
func isDot(ch byte) bool {
56,323✔
772
        return ch == '.'
56,323✔
773
}
56,323✔
774

775
func newCreateTableStmt(
776
        name string,
777
        elems []TableElem,
778
        ifNotExists bool,
779
) *CreateTableStmt {
513✔
780
        colsSpecs := make([]*ColSpec, 0, 5)
513✔
781
        var checks []CheckConstraint
513✔
782

513✔
783
        var pk PrimaryKeyConstraint
513✔
784
        for _, e := range elems {
2,089✔
785
                switch c := e.(type) {
1,576✔
786
                case *ColSpec:
1,147✔
787
                        colsSpecs = append(colsSpecs, c)
1,147✔
788
                case PrimaryKeyConstraint:
418✔
789
                        pk = c
418✔
790
                case CheckConstraint:
10✔
791
                        if checks == nil {
15✔
792
                                checks = make([]CheckConstraint, 0, 5)
5✔
793
                        }
5✔
794
                        checks = append(checks, c)
10✔
795
                }
796
        }
797

798
        return &CreateTableStmt{
513✔
799
                ifNotExists: ifNotExists,
513✔
800
                table:       name,
513✔
801
                colsSpec:    colsSpecs,
513✔
802
                pkColNames:  pk,
513✔
803
                checks:      checks,
513✔
804
        }
513✔
805
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc