Skip to content

Commit 9e2cc93

Browse files
author
Jaco Labuschagne
committed
Add integration tests for statement type identification
- Introduced a new integration test in `integration_test.go` to validate the correct identification of various PL/SQL statement types from a complex script. - Updated `models.go` to use the new `statement.Type` for better type safety. - Refactored existing tests in `splitter_test.go` to utilize the `statement.Type` constants instead of string literals for statement types. - Added comprehensive tests for JSON marshaling and unmarshaling of statement types in `type_test.go`. - Created a new `type.go` file to define statement types and their associated helper methods for categorization.
1 parent 24acf6a commit 9e2cc93

6 files changed

Lines changed: 564 additions & 18 deletions

File tree

pkg/splitter/integration_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package splitter
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/zodimo/go-plsql-statement-splitter/pkg/statement"
8+
)
9+
10+
func TestTypeSafeStatementType(t *testing.T) {
11+
// This is a complex script with multiple statement types
12+
script := `
13+
-- Simple DML statements
14+
SELECT * FROM employees;
15+
INSERT INTO employees (id, name) VALUES (1, 'John');
16+
UPDATE employees SET salary = 1000 WHERE id = 1;
17+
DELETE FROM employees WHERE id = 2;
18+
MERGE INTO employees USING temp_employees ON (employees.id = temp_employees.id);
19+
20+
-- DDL statements
21+
CREATE TABLE customers (id NUMBER, name VARCHAR2(100));
22+
ALTER TABLE customers ADD email VARCHAR2(100);
23+
DROP TABLE old_customers;
24+
TRUNCATE TABLE empty_table;
25+
GRANT SELECT ON employees TO hr_user;
26+
REVOKE DELETE ON employees FROM hr_user;
27+
28+
-- Transaction statements
29+
COMMIT;
30+
ROLLBACK;
31+
SAVEPOINT sp1;
32+
SET TRANSACTION READ ONLY;
33+
34+
-- PL/SQL blocks
35+
BEGIN
36+
DBMS_OUTPUT.PUT_LINE('Hello, World!');
37+
END;
38+
/
39+
40+
CREATE OR REPLACE PROCEDURE hello_world IS
41+
BEGIN
42+
DBMS_OUTPUT.PUT_LINE('Hello, World!');
43+
END;
44+
/
45+
`
46+
47+
// Split the script
48+
statements, err := SplitString(script)
49+
if err != nil {
50+
t.Fatalf("Failed to split script: %v", err)
51+
}
52+
53+
// Define expected statement types
54+
expectedTypes := map[string]statement.Type{
55+
"SELECT": statement.TypeSelect,
56+
"INSERT": statement.TypeInsert,
57+
"UPDATE": statement.TypeUpdate,
58+
"DELETE": statement.TypeDelete,
59+
"MERGE": statement.TypeMerge,
60+
"CREATE TABLE": statement.TypeCreateTable,
61+
"ALTER TABLE": statement.TypeAlterTable,
62+
"DROP TABLE": statement.TypeDropTable,
63+
"TRUNCATE": statement.TypeTruncate,
64+
"GRANT": statement.TypeGrant,
65+
"REVOKE": statement.TypeRevoke,
66+
"COMMIT": statement.TypeCommit,
67+
"ROLLBACK": statement.TypeRollback,
68+
"SAVEPOINT": statement.TypeSavepoint,
69+
"PLSQL_BLOCK": statement.TypePlsqlBlock,
70+
"CREATE_PROCEDURE": statement.TypeCreateProcedure,
71+
}
72+
73+
// Keep track of statement types found
74+
foundTypes := make(map[statement.Type]bool)
75+
76+
// Verify statements have correct types
77+
for _, stmt := range statements {
78+
// Log statement for debugging
79+
t.Logf("Found statement type %s: %.40s...", stmt.Type, strings.ReplaceAll(stmt.Content, "\n", " "))
80+
81+
// Record that we found this type
82+
foundTypes[stmt.Type] = true
83+
84+
// Check if the statement type starts with an expected prefix
85+
prefix := strings.SplitN(strings.TrimSpace(stmt.Content), " ", 2)[0]
86+
if prefix == "/" {
87+
continue // Skip forward slash - it's special
88+
}
89+
90+
// Check helper methods
91+
if strings.Contains(strings.ToUpper(stmt.Content), "SELECT") &&
92+
!stmt.Type.IsDML() &&
93+
stmt.Type != statement.TypeCreateView &&
94+
!strings.HasPrefix(strings.ToUpper(strings.TrimSpace(stmt.Content)), "GRANT") {
95+
t.Errorf("Statement with SELECT should have IsDML() return true: %s", stmt.Content)
96+
}
97+
98+
if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(stmt.Content)), "CREATE") && !stmt.Type.IsDDL() {
99+
t.Errorf("Statement with CREATE should have IsDDL() return true: %s", stmt.Content)
100+
}
101+
102+
if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(stmt.Content)), "BEGIN") && !stmt.Type.IsPLSQL() {
103+
t.Errorf("Statement with BEGIN should have IsPLSQL() return true: %s", stmt.Content)
104+
}
105+
106+
if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(stmt.Content)), "COMMIT") && !stmt.Type.IsTransactional() {
107+
t.Errorf("Statement with COMMIT should have IsTransactional() return true: %s", stmt.Content)
108+
}
109+
}
110+
111+
// Check that we found all the expected types
112+
for keyword, expectedType := range expectedTypes {
113+
if strings.Contains(script, keyword) && !foundTypes[expectedType] {
114+
// Check if we found any statement with content containing the keyword
115+
foundInContent := false
116+
for _, stmt := range statements {
117+
if strings.Contains(strings.ToUpper(stmt.Content), strings.ToUpper(keyword)) {
118+
foundInContent = true
119+
break
120+
}
121+
}
122+
123+
if !foundInContent {
124+
t.Errorf("Script contains %s but no statement with type %s was found", keyword, expectedType)
125+
}
126+
}
127+
}
128+
}

pkg/splitter/models.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package splitter
22

3+
import (
4+
"github.com/zodimo/go-plsql-statement-splitter/pkg/statement"
5+
)
6+
37
// Statement represents a single PL/SQL statement with position information
48
type Statement struct {
5-
Content string `json:"content"`
6-
StartLine int `json:"startLine"`
7-
EndLine int `json:"endLine"`
8-
StartColumn int `json:"startColumn"`
9-
EndColumn int `json:"endColumn"`
10-
Type string `json:"type,omitempty"` // If available from ANTLR parser
9+
Content string `json:"content"`
10+
StartLine int `json:"startLine"`
11+
EndLine int `json:"endLine"`
12+
StartColumn int `json:"startColumn"`
13+
EndColumn int `json:"endColumn"`
14+
Type statement.Type `json:"type,omitempty"` // If available from ANTLR parser
1115
}
1216

1317
// SyntaxError represents a syntax error in a PL/SQL script

pkg/splitter/splitter.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
internalParser "github.com/zodimo/go-plsql-statement-splitter/internal/parser"
11+
"github.com/zodimo/go-plsql-statement-splitter/pkg/statement"
1112
)
1213

1314
// Splitter is responsible for splitting PL/SQL scripts into individual statements
@@ -203,7 +204,7 @@ func (s *Splitter) SplitString(content string) ([]Statement, error) {
203204
for _, stmt := range parsedStatements {
204205
statement := Statement{
205206
Content: stmt.Content,
206-
Type: stmt.Type,
207+
Type: statement.Parse(stmt.Type),
207208
}
208209

209210
// Include position information if configured

pkg/splitter/splitter_test.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77
"testing"
88

9+
"github.com/zodimo/go-plsql-statement-splitter/pkg/statement"
910
"github.com/zodimo/go-plsql-statement-splitter/test/samples"
1011
)
1112

@@ -31,16 +32,16 @@ func TestSplitString_SingleStatement(t *testing.T) {
3132
if statements[0].Content != input {
3233
t.Errorf("Expected content to be %q, got %q", input, statements[0].Content)
3334
}
34-
if statements[0].Type != "SELECT" {
35+
if statements[0].Type != statement.TypeSelect {
3536
t.Errorf("Expected type to be SELECT, got %s", statements[0].Type)
3637
}
3738
}
3839

3940
func TestSplitString_MultipleStatements(t *testing.T) {
4041
input := `
4142
SELECT * FROM employees;
42-
INSERT INTO employees (id, name) VALUES (1, 'John');
43-
UPDATE employees SET name = 'Jane' WHERE id = 1;
43+
INSERT INTO employees VALUES (1, 'John');
44+
UPDATE employees SET salary = 1000;
4445
`
4546
statements, err := SplitString(input)
4647
if err != nil {
@@ -51,7 +52,7 @@ func TestSplitString_MultipleStatements(t *testing.T) {
5152
}
5253

5354
// Verify the types of statements
54-
expectedTypes := []string{"SELECT", "INSERT", "UPDATE"}
55+
expectedTypes := []statement.Type{statement.TypeSelect, statement.TypeInsert, statement.TypeUpdate}
5556
for i, stmt := range statements {
5657
if i < len(expectedTypes) && stmt.Type != expectedTypes[i] {
5758
t.Errorf("Statement %d: expected type %s, got %s", i, expectedTypes[i], stmt.Type)
@@ -80,7 +81,7 @@ func TestSplitString_PlSqlBlock(t *testing.T) {
8081
foundBlock := false
8182

8283
for _, stmt := range statements {
83-
if stmt.Type == "PLSQL_BLOCK" {
84+
if stmt.Type == statement.TypePlsqlBlock {
8485
foundBlock = true
8586
}
8687
}
@@ -93,7 +94,7 @@ func TestSplitString_PlSqlBlock(t *testing.T) {
9394
// To check for slash, uncomment and add the following:
9495
// foundSlash := false
9596
// for _, stmt := range statements {
96-
// if stmt.Type == "SLASH" {
97+
// if stmt.Type == statement.TypeSlash {
9798
// foundSlash = true
9899
// }
99100
// }
@@ -130,7 +131,7 @@ func TestSplitString_CreateProcedure(t *testing.T) {
130131
// or contains CREATE PROCEDURE in the content
131132
foundProcedure := false
132133
for _, stmt := range statements {
133-
if stmt.Type == "CREATE_PROCEDURE" || strings.Contains(strings.ToUpper(stmt.Content), "CREATE PROCEDURE") {
134+
if stmt.Type == statement.TypeCreateProcedure || strings.Contains(strings.ToUpper(stmt.Content), "CREATE PROCEDURE") {
134135
foundProcedure = true
135136
break
136137
}
@@ -155,7 +156,7 @@ func TestSplitReader(t *testing.T) {
155156
if statements[0].Content != input {
156157
t.Errorf("Expected content to be %q, got %q", input, statements[0].Content)
157158
}
158-
if statements[0].Type != "SELECT" {
159+
if statements[0].Type != statement.TypeSelect {
159160
t.Errorf("Expected type to be SELECT, got %s", statements[0].Type)
160161
}
161162
}
@@ -311,7 +312,7 @@ func TestSplitFile(t *testing.T) {
311312
if statements[0].Content != content {
312313
t.Errorf("Expected content to be %q, got %q", content, statements[0].Content)
313314
}
314-
if statements[0].Type != "SELECT" {
315+
if statements[0].Type != statement.TypeSelect {
315316
t.Errorf("Expected type to be SELECT, got %s", statements[0].Type)
316317
}
317318

@@ -352,7 +353,7 @@ func TestSplitString_SimpleSQLSamples(t *testing.T) {
352353
}
353354

354355
// Check that type is set (even if empty)
355-
if stmt.Type == "" {
356+
if stmt.Type == statement.Type("") {
356357
t.Errorf("Statement %d has no type", i)
357358
}
358359
}
@@ -389,7 +390,7 @@ func TestSplitString_ComplexSQLSamples(t *testing.T) {
389390
}
390391

391392
// Check that type is set (even if empty)
392-
if stmt.Type == "" {
393+
if stmt.Type == statement.Type("") {
393394
t.Errorf("Statement %d has no type", i)
394395
}
395396
}

0 commit comments

Comments
 (0)