forked from pgpkg/pgpkg
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathunit.go
More file actions
134 lines (109 loc) · 3.25 KB
/
unit.go
File metadata and controls
134 lines (109 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package pgpkg
//
// This file is the unit parser for pgpkg. It uses the official postgresql parser to
// parse a package into a set of build units (Units), consisting of SQL statements.
//
import (
pg_query "github.com/pganalyze/pg_query_go/v5"
"io"
"strings"
)
// Unit (ie, build unit) represents potentially parsable tree of SQL source code
// taken from a single file. Units are lazily loaded, and don't parse their
// contents until requested, with the Statements() function.
// Once the unit is compiled, individual statements contain line number and other
// debugging information.
type Unit struct {
// The Bundle that this unit belongs to.
Bundle *Bundle
// Path is the filename within the Bundle FS that this Unit
// should read from when it's parsed.
Path string
// The contents (SQL statements) declared in the unit.
Source string
// The list of parsed statements in this unit.
Statements []*Statement
}
// Add a statement to a unit. The parser will include all whitespace and comments prefixing
// the first line of code, so we remove that and increase the line number to give us the first
// line of the actual code.
func (u *Unit) addStatement(lineNumber int, sql string, tree *pg_query.RawStmt) {
// Skip over empty lines or lines that start with "--".
lines := strings.Split(sql, "\n")
skipped := 0
offset := 0
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, "--") {
break
}
skipped++
offset += len(line) + 1
}
statement := &Statement{
Unit: u,
LineNumber: lineNumber + skipped,
Tree: tree,
}
if offset > len(sql) {
statement.Source = sql
} else {
statement.Source = sql[offset:]
}
u.Statements = append(u.Statements, statement)
}
// Parse a unit.
func (u *Unit) Parse() error {
r, err := u.Bundle.Open(u.Path)
if err != nil {
return PKGErrorf(u, err, "unable to open")
}
b, err := io.ReadAll(r)
if err != nil {
return PKGErrorf(u, err, "unable to read")
}
source := strings.TrimSpace(string(b))
// Empty files are OK.
if source == "" {
return nil
}
// Files starting with "--pgpkg:ignore" are ignored
if strings.HasPrefix(source, "--pgpkg:ignore") {
return nil
}
// Automatically add a semicolon to the source if one
// isn't there already.
if source[len(source)-1] != ';' {
source = source + ";"
}
u.Source = source
parseResult, err := pg_query.Parse(source)
if err != nil {
// unfortunately parser errors return almost no information, so the best
// we can do is identify the build unit. This seems to be a problem with
// pg_query_go rather than the underlying PG parser itself.
return PKGErrorf(u, err, "unable to parse")
}
lineNumber := 1
// Add the statements to the unit.
for _, stmt := range parseResult.Stmts {
sql := source[stmt.StmtLocation : stmt.StmtLocation+stmt.StmtLen]
u.addStatement(lineNumber, sql, stmt)
// find all the \n's in the statement, which will give us the new line number.
lineNumber = lineNumber + strings.Count(sql, "\n")
}
return nil
}
func (u *Unit) Location() string {
if u != nil {
return u.Bundle.Location() + "/" + u.Path
} else {
return "<internal>"
}
}
func (u *Unit) DefaultContext() *PKGErrorContext {
return &PKGErrorContext{
Source: u.Source,
LineNumber: 1,
}
}