Skip to content

Commit 363b12e

Browse files
committed
Fix #178.
1 parent 90d6ec3 commit 363b12e

File tree

4 files changed

+131
-15
lines changed

4 files changed

+131
-15
lines changed

context.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func (ctx Context) ResultText(value string) {
8989
}
9090

9191
// ResultRawText sets the text result of the function to a []byte.
92+
// Returning a nil slice is the same as calling [Context.ResultNull].
9293
//
9394
// https://sqlite.org/c3ref/result_blob.html
9495
func (ctx Context) ResultRawText(value []byte) {

ext/regexp/regexp.go

Lines changed: 114 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//
33
// It provides the following Unicode aware functions:
44
// - regexp_like(),
5+
// - regexp_count(),
6+
// - regexp_instr(),
57
// - regexp_substr(),
68
// - regexp_replace(),
79
// - and a REGEXP operator.
@@ -24,8 +26,17 @@ func Register(db *sqlite3.Conn) error {
2426
return errors.Join(
2527
db.CreateFunction("regexp", 2, flags, regex),
2628
db.CreateFunction("regexp_like", 2, flags, regexLike),
29+
db.CreateFunction("regexp_count", 2, flags, regexCount),
30+
db.CreateFunction("regexp_count", 3, flags, regexCount),
31+
db.CreateFunction("regexp_instr", 2, flags, regexInstr),
32+
db.CreateFunction("regexp_instr", 3, flags, regexInstr),
33+
db.CreateFunction("regexp_instr", 4, flags, regexInstr),
34+
db.CreateFunction("regexp_instr", 5, flags, regexInstr),
2735
db.CreateFunction("regexp_substr", 2, flags, regexSubstr),
28-
db.CreateFunction("regexp_replace", 3, flags, regexReplace))
36+
db.CreateFunction("regexp_substr", 3, flags, regexSubstr),
37+
db.CreateFunction("regexp_substr", 4, flags, regexSubstr),
38+
db.CreateFunction("regexp_replace", 3, flags, regexReplace),
39+
db.CreateFunction("regexp_replace", 4, flags, regexReplace))
2940
}
3041

3142
func load(ctx sqlite3.Context, i int, expr string) (*regexp.Regexp, error) {
@@ -44,35 +55,126 @@ func load(ctx sqlite3.Context, i int, expr string) (*regexp.Regexp, error) {
4455
func regex(ctx sqlite3.Context, arg ...sqlite3.Value) {
4556
re, err := load(ctx, 0, arg[0].Text())
4657
if err != nil {
47-
ctx.ResultError(err) // notest
48-
} else {
49-
ctx.ResultBool(re.Match(arg[1].RawText()))
58+
ctx.ResultError(err)
59+
return // notest
5060
}
61+
text := arg[1].RawText()
62+
ctx.ResultBool(re.Match(text))
5163
}
5264

5365
func regexLike(ctx sqlite3.Context, arg ...sqlite3.Value) {
5466
re, err := load(ctx, 1, arg[1].Text())
5567
if err != nil {
56-
ctx.ResultError(err) // notest
57-
} else {
58-
ctx.ResultBool(re.Match(arg[0].RawText()))
68+
ctx.ResultError(err)
69+
return // notest
5970
}
71+
text := arg[0].RawText()
72+
ctx.ResultBool(re.Match(text))
73+
}
74+
75+
func regexCount(ctx sqlite3.Context, arg ...sqlite3.Value) {
76+
re, err := load(ctx, 1, arg[1].Text())
77+
if err != nil {
78+
ctx.ResultError(err)
79+
return // notest
80+
}
81+
text := arg[0].RawText()
82+
if len(arg) > 2 {
83+
pos := arg[2].Int()
84+
_, text = split(text, pos)
85+
}
86+
ctx.ResultInt(len(re.FindAll(text, -1)))
6087
}
6188

6289
func regexSubstr(ctx sqlite3.Context, arg ...sqlite3.Value) {
6390
re, err := load(ctx, 1, arg[1].Text())
6491
if err != nil {
65-
ctx.ResultError(err) // notest
92+
ctx.ResultError(err)
93+
return // notest
94+
}
95+
text := arg[0].RawText()
96+
if len(arg) > 2 {
97+
pos := arg[2].Int()
98+
_, text = split(text, pos)
99+
}
100+
n := 0
101+
if len(arg) > 3 {
102+
n = arg[3].Int()
103+
}
104+
105+
var res []byte
106+
if n <= 1 {
107+
res = re.Find(text)
66108
} else {
67-
ctx.ResultRawText(re.Find(arg[0].RawText()))
109+
all := re.FindAll(text, n)
110+
if n <= len(all) {
111+
res = all[n-1]
112+
}
68113
}
114+
ctx.ResultRawText(res)
69115
}
70116

71-
func regexReplace(ctx sqlite3.Context, arg ...sqlite3.Value) {
117+
func regexInstr(ctx sqlite3.Context, arg ...sqlite3.Value) {
72118
re, err := load(ctx, 1, arg[1].Text())
73119
if err != nil {
74-
ctx.ResultError(err) // notest
120+
ctx.ResultError(err)
121+
return // notest
122+
}
123+
pos := 1
124+
text := arg[0].RawText()
125+
if len(arg) > 2 {
126+
pos = arg[2].Int()
127+
_, text = split(text, pos)
128+
}
129+
n := 0
130+
if len(arg) > 3 {
131+
n = arg[3].Int()
132+
}
133+
134+
var loc []int
135+
if n <= 1 {
136+
loc = re.FindIndex(text)
75137
} else {
76-
ctx.ResultRawText(re.ReplaceAll(arg[0].RawText(), arg[2].RawText()))
138+
all := re.FindAllIndex(text, n)
139+
if n <= len(all) {
140+
loc = all[n-1]
141+
}
142+
}
143+
if loc == nil {
144+
return
145+
}
146+
147+
end := 0
148+
if len(arg) > 4 && arg[4].Bool() {
149+
end = 1
150+
}
151+
ctx.ResultInt(pos + loc[end])
152+
}
153+
154+
func regexReplace(ctx sqlite3.Context, arg ...sqlite3.Value) {
155+
re, err := load(ctx, 1, arg[1].Text())
156+
if err != nil {
157+
ctx.ResultError(err)
158+
return // notest
159+
}
160+
var head, tail []byte
161+
tail = arg[0].RawText()
162+
if len(arg) > 3 {
163+
pos := arg[3].Int()
164+
head, tail = split(tail, pos)
165+
}
166+
tail = re.ReplaceAll(tail, arg[2].RawText())
167+
if head != nil {
168+
tail = append(head, tail...)
169+
}
170+
ctx.ResultRawText(tail)
171+
}
172+
173+
func split(s []byte, i int) (head, tail []byte) {
174+
for pos := range string(s) {
175+
if i--; i <= 0 {
176+
return s[:pos:pos], s[pos:]
177+
}
77178
}
179+
return s, nil
78180
}

ext/regexp/regexp_test.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package regexp
22

33
import (
4+
"database/sql"
45
"testing"
56

67
"github.com/ncruces/go-sqlite3/driver"
@@ -29,18 +30,27 @@ func TestRegister(t *testing.T) {
2930
{`regexp_like('Hello', 'elo')`, "0"},
3031
{`regexp_like('Hello', 'ell')`, "1"},
3132
{`regexp_like('Hello', 'el.')`, "1"},
33+
{`regexp_count('Hello', 'l')`, "2"},
34+
{`regexp_instr('Hello', 'el.')`, "2"},
35+
{`regexp_instr('Hello', '.', 6)`, ""},
3236
{`regexp_substr('Hello', 'el.')`, "ell"},
37+
{`regexp_substr('Hello', 'l', 2, 2)`, "l"},
3338
{`regexp_replace('Hello', 'llo', 'll')`, "Hell"},
39+
40+
{`regexp_count('123123123123123', '(12)3', 1)`, "5"},
41+
{`regexp_instr('500 Oracle Parkway, Redwood Shores, CA', '(?i)[s|r|p][[:alpha:]]{6}', 3, 2, 1)`, "28"},
42+
{`regexp_substr('500 Oracle Parkway, Redwood Shores, CA', ',[^,]+,', 3, 1)`, ", Redwood Shores,"},
43+
{`regexp_replace('500 Oracle Parkway, Redwood Shores, CA', '( ){2,}', ' ', 3)`, "500 Oracle Parkway, Redwood Shores, CA"},
3444
}
3545

3646
for _, tt := range tests {
37-
var got string
47+
var got sql.NullString
3848
err := db.QueryRow(`SELECT ` + tt.test).Scan(&got)
3949
if err != nil {
4050
t.Fatal(err)
4151
}
42-
if got != tt.want {
43-
t.Errorf("got %q, want %q", got, tt.want)
52+
if got.String != tt.want {
53+
t.Errorf("got %q, want %q", got.String, tt.want)
4454
}
4555
}
4656
}
@@ -58,6 +68,8 @@ func TestRegister_errors(t *testing.T) {
5868
tests := []string{
5969
`'' REGEXP ?`,
6070
`regexp_like('', ?)`,
71+
`regexp_count('', ?)`,
72+
`regexp_instr('', ?)`,
6173
`regexp_substr('', ?)`,
6274
`regexp_replace('', ?, '')`,
6375
}

stmt.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ func (s *Stmt) BindText(param int, value string) error {
255255

256256
// BindRawText binds a []byte to the prepared statement as text.
257257
// The leftmost SQL parameter has an index of 1.
258+
// Binding a nil slice is the same as calling [Stmt.BindNull].
258259
//
259260
// https://sqlite.org/c3ref/bind_blob.html
260261
func (s *Stmt) BindRawText(param int, value []byte) error {

0 commit comments

Comments
 (0)