mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
Fix #178.
This commit is contained in:
@@ -89,6 +89,7 @@ func (ctx Context) ResultText(value string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResultRawText sets the text result of the function to a []byte.
|
// ResultRawText sets the text result of the function to a []byte.
|
||||||
|
// Returning a nil slice is the same as calling [Context.ResultNull].
|
||||||
//
|
//
|
||||||
// https://sqlite.org/c3ref/result_blob.html
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
func (ctx Context) ResultRawText(value []byte) {
|
func (ctx Context) ResultRawText(value []byte) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
//
|
//
|
||||||
// It provides the following Unicode aware functions:
|
// It provides the following Unicode aware functions:
|
||||||
// - regexp_like(),
|
// - regexp_like(),
|
||||||
|
// - regexp_count(),
|
||||||
|
// - regexp_instr(),
|
||||||
// - regexp_substr(),
|
// - regexp_substr(),
|
||||||
// - regexp_replace(),
|
// - regexp_replace(),
|
||||||
// - and a REGEXP operator.
|
// - and a REGEXP operator.
|
||||||
@@ -24,8 +26,17 @@ func Register(db *sqlite3.Conn) error {
|
|||||||
return errors.Join(
|
return errors.Join(
|
||||||
db.CreateFunction("regexp", 2, flags, regex),
|
db.CreateFunction("regexp", 2, flags, regex),
|
||||||
db.CreateFunction("regexp_like", 2, flags, regexLike),
|
db.CreateFunction("regexp_like", 2, flags, regexLike),
|
||||||
|
db.CreateFunction("regexp_count", 2, flags, regexCount),
|
||||||
|
db.CreateFunction("regexp_count", 3, flags, regexCount),
|
||||||
|
db.CreateFunction("regexp_instr", 2, flags, regexInstr),
|
||||||
|
db.CreateFunction("regexp_instr", 3, flags, regexInstr),
|
||||||
|
db.CreateFunction("regexp_instr", 4, flags, regexInstr),
|
||||||
|
db.CreateFunction("regexp_instr", 5, flags, regexInstr),
|
||||||
db.CreateFunction("regexp_substr", 2, flags, regexSubstr),
|
db.CreateFunction("regexp_substr", 2, flags, regexSubstr),
|
||||||
db.CreateFunction("regexp_replace", 3, flags, regexReplace))
|
db.CreateFunction("regexp_substr", 3, flags, regexSubstr),
|
||||||
|
db.CreateFunction("regexp_substr", 4, flags, regexSubstr),
|
||||||
|
db.CreateFunction("regexp_replace", 3, flags, regexReplace),
|
||||||
|
db.CreateFunction("regexp_replace", 4, flags, regexReplace))
|
||||||
}
|
}
|
||||||
|
|
||||||
func load(ctx sqlite3.Context, i int, expr string) (*regexp.Regexp, error) {
|
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) {
|
|||||||
func regex(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
func regex(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||||
re, err := load(ctx, 0, arg[0].Text())
|
re, err := load(ctx, 0, arg[0].Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ResultError(err) // notest
|
ctx.ResultError(err)
|
||||||
} else {
|
return // notest
|
||||||
ctx.ResultBool(re.Match(arg[1].RawText()))
|
|
||||||
}
|
}
|
||||||
|
text := arg[1].RawText()
|
||||||
|
ctx.ResultBool(re.Match(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
func regexLike(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
func regexLike(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||||
re, err := load(ctx, 1, arg[1].Text())
|
re, err := load(ctx, 1, arg[1].Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ResultError(err) // notest
|
ctx.ResultError(err)
|
||||||
} else {
|
return // notest
|
||||||
ctx.ResultBool(re.Match(arg[0].RawText()))
|
|
||||||
}
|
}
|
||||||
|
text := arg[0].RawText()
|
||||||
|
ctx.ResultBool(re.Match(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func regexCount(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||||
|
re, err := load(ctx, 1, arg[1].Text())
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResultError(err)
|
||||||
|
return // notest
|
||||||
|
}
|
||||||
|
text := arg[0].RawText()
|
||||||
|
if len(arg) > 2 {
|
||||||
|
pos := arg[2].Int()
|
||||||
|
_, text = split(text, pos)
|
||||||
|
}
|
||||||
|
ctx.ResultInt(len(re.FindAll(text, -1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func regexSubstr(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
func regexSubstr(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||||
re, err := load(ctx, 1, arg[1].Text())
|
re, err := load(ctx, 1, arg[1].Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ResultError(err) // notest
|
ctx.ResultError(err)
|
||||||
} else {
|
return // notest
|
||||||
ctx.ResultRawText(re.Find(arg[0].RawText()))
|
|
||||||
}
|
}
|
||||||
|
text := arg[0].RawText()
|
||||||
|
if len(arg) > 2 {
|
||||||
|
pos := arg[2].Int()
|
||||||
|
_, text = split(text, pos)
|
||||||
|
}
|
||||||
|
n := 0
|
||||||
|
if len(arg) > 3 {
|
||||||
|
n = arg[3].Int()
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []byte
|
||||||
|
if n <= 1 {
|
||||||
|
res = re.Find(text)
|
||||||
|
} else {
|
||||||
|
all := re.FindAll(text, n)
|
||||||
|
if n <= len(all) {
|
||||||
|
res = all[n-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.ResultRawText(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func regexInstr(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||||
|
re, err := load(ctx, 1, arg[1].Text())
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResultError(err)
|
||||||
|
return // notest
|
||||||
|
}
|
||||||
|
pos := 1
|
||||||
|
text := arg[0].RawText()
|
||||||
|
if len(arg) > 2 {
|
||||||
|
pos = arg[2].Int()
|
||||||
|
_, text = split(text, pos)
|
||||||
|
}
|
||||||
|
n := 0
|
||||||
|
if len(arg) > 3 {
|
||||||
|
n = arg[3].Int()
|
||||||
|
}
|
||||||
|
|
||||||
|
var loc []int
|
||||||
|
if n <= 1 {
|
||||||
|
loc = re.FindIndex(text)
|
||||||
|
} else {
|
||||||
|
all := re.FindAllIndex(text, n)
|
||||||
|
if n <= len(all) {
|
||||||
|
loc = all[n-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if loc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
end := 0
|
||||||
|
if len(arg) > 4 && arg[4].Bool() {
|
||||||
|
end = 1
|
||||||
|
}
|
||||||
|
ctx.ResultInt(pos + loc[end])
|
||||||
}
|
}
|
||||||
|
|
||||||
func regexReplace(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
func regexReplace(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||||
re, err := load(ctx, 1, arg[1].Text())
|
re, err := load(ctx, 1, arg[1].Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ResultError(err) // notest
|
ctx.ResultError(err)
|
||||||
} else {
|
return // notest
|
||||||
ctx.ResultRawText(re.ReplaceAll(arg[0].RawText(), arg[2].RawText()))
|
|
||||||
}
|
}
|
||||||
|
var head, tail []byte
|
||||||
|
tail = arg[0].RawText()
|
||||||
|
if len(arg) > 3 {
|
||||||
|
pos := arg[3].Int()
|
||||||
|
head, tail = split(tail, pos)
|
||||||
|
}
|
||||||
|
tail = re.ReplaceAll(tail, arg[2].RawText())
|
||||||
|
if head != nil {
|
||||||
|
tail = append(head, tail...)
|
||||||
|
}
|
||||||
|
ctx.ResultRawText(tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(s []byte, i int) (head, tail []byte) {
|
||||||
|
for pos := range string(s) {
|
||||||
|
if i--; i <= 0 {
|
||||||
|
return s[:pos:pos], s[pos:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package regexp
|
package regexp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ncruces/go-sqlite3/driver"
|
"github.com/ncruces/go-sqlite3/driver"
|
||||||
@@ -29,18 +30,27 @@ func TestRegister(t *testing.T) {
|
|||||||
{`regexp_like('Hello', 'elo')`, "0"},
|
{`regexp_like('Hello', 'elo')`, "0"},
|
||||||
{`regexp_like('Hello', 'ell')`, "1"},
|
{`regexp_like('Hello', 'ell')`, "1"},
|
||||||
{`regexp_like('Hello', 'el.')`, "1"},
|
{`regexp_like('Hello', 'el.')`, "1"},
|
||||||
|
{`regexp_count('Hello', 'l')`, "2"},
|
||||||
|
{`regexp_instr('Hello', 'el.')`, "2"},
|
||||||
|
{`regexp_instr('Hello', '.', 6)`, ""},
|
||||||
{`regexp_substr('Hello', 'el.')`, "ell"},
|
{`regexp_substr('Hello', 'el.')`, "ell"},
|
||||||
|
{`regexp_substr('Hello', 'l', 2, 2)`, "l"},
|
||||||
{`regexp_replace('Hello', 'llo', 'll')`, "Hell"},
|
{`regexp_replace('Hello', 'llo', 'll')`, "Hell"},
|
||||||
|
|
||||||
|
{`regexp_count('123123123123123', '(12)3', 1)`, "5"},
|
||||||
|
{`regexp_instr('500 Oracle Parkway, Redwood Shores, CA', '(?i)[s|r|p][[:alpha:]]{6}', 3, 2, 1)`, "28"},
|
||||||
|
{`regexp_substr('500 Oracle Parkway, Redwood Shores, CA', ',[^,]+,', 3, 1)`, ", Redwood Shores,"},
|
||||||
|
{`regexp_replace('500 Oracle Parkway, Redwood Shores, CA', '( ){2,}', ' ', 3)`, "500 Oracle Parkway, Redwood Shores, CA"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
var got string
|
var got sql.NullString
|
||||||
err := db.QueryRow(`SELECT ` + tt.test).Scan(&got)
|
err := db.QueryRow(`SELECT ` + tt.test).Scan(&got)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if got.String != tt.want {
|
||||||
t.Errorf("got %q, want %q", got, tt.want)
|
t.Errorf("got %q, want %q", got.String, tt.want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,6 +68,8 @@ func TestRegister_errors(t *testing.T) {
|
|||||||
tests := []string{
|
tests := []string{
|
||||||
`'' REGEXP ?`,
|
`'' REGEXP ?`,
|
||||||
`regexp_like('', ?)`,
|
`regexp_like('', ?)`,
|
||||||
|
`regexp_count('', ?)`,
|
||||||
|
`regexp_instr('', ?)`,
|
||||||
`regexp_substr('', ?)`,
|
`regexp_substr('', ?)`,
|
||||||
`regexp_replace('', ?, '')`,
|
`regexp_replace('', ?, '')`,
|
||||||
}
|
}
|
||||||
|
|||||||
1
stmt.go
1
stmt.go
@@ -255,6 +255,7 @@ func (s *Stmt) BindText(param int, value string) error {
|
|||||||
|
|
||||||
// BindRawText binds a []byte to the prepared statement as text.
|
// BindRawText binds a []byte to the prepared statement as text.
|
||||||
// The leftmost SQL parameter has an index of 1.
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
// Binding a nil slice is the same as calling [Stmt.BindNull].
|
||||||
//
|
//
|
||||||
// https://sqlite.org/c3ref/bind_blob.html
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
func (s *Stmt) BindRawText(param int, value []byte) error {
|
func (s *Stmt) BindRawText(param int, value []byte) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user