diff --git a/libsqlstore/go.mod b/libsqlstore/go.mod new file mode 100644 index 0000000..a7b78e8 --- /dev/null +++ b/libsqlstore/go.mod @@ -0,0 +1,8 @@ +module github.com/alexedwards/scs/libsqlstore + +go 1.12 + +require ( + github.com/mattn/go-sqlite3 v1.14.16 + github.com/tursodatabase/libsql-client-go v0.0.0-20231216154754-8383a53d618f // indirect +) diff --git a/libsqlstore/go.sum b/libsqlstore/go.sum new file mode 100644 index 0000000..2355623 --- /dev/null +++ b/libsqlstore/go.sum @@ -0,0 +1,146 @@ +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 h1:6PfEMwfInASh9hkN83aR0j4W/eKaAZt/AURtXAXlas0= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475/go.mod h1:20nXSmcf0nAscrzqsXeC2/tA3KkV2eCiJqYuyAgl+ss= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tursodatabase/libsql-client-go v0.0.0-20231216154754-8383a53d618f h1:teZ0Pj1Wp3Wk0JObKBiKZqgxhYwLeJhVAyj6DRgmQtY= +github.com/tursodatabase/libsql-client-go v0.0.0-20231216154754-8383a53d618f/go.mod h1:UMde0InJz9I0Le/1YIR4xsB0E2vb01MrDY6k/eNdfkg= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= +modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= +modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= +modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/libsqlstore/libsqlstore.go b/libsqlstore/libsqlstore.go new file mode 100644 index 0000000..f6bbc60 --- /dev/null +++ b/libsqlstore/libsqlstore.go @@ -0,0 +1,138 @@ +package libsqlstore + +import ( + "database/sql" + "log" + "time" + + _ "github.com/mattn/go-sqlite3" + + _ "github.com/tursodatabase/libsql-client-go/libsql" +) + +// SQLite3Store represents the session store. +type LibsqlStore struct { + db *sql.DB + stopCleanup chan bool +} + +// New returns a new SQLite3Store instance, with a background cleanup goroutine +// that runs every 5 minutes to remove expired session data. +func New(db *sql.DB) *LibsqlStore { + return NewWithCleanupInterval(db, 5*time.Minute) +} + +// NewWithCleanupInterval returns a new LibsqlStore instance. The cleanupInterval +// parameter controls how frequently expired session data is removed by the +// background cleanup goroutine. Setting it to 0 prevents the cleanup goroutine +// from running (i.e. expired sessions will not be removed). +func NewWithCleanupInterval(db *sql.DB, cleanupInterval time.Duration) *LibsqlStore { + p := &LibsqlStore{db: db} + if cleanupInterval > 0 { + go p.startCleanup(cleanupInterval) + } + return p +} + +// Find returns the data for a given session token from the LibsqlStore instance. +// If the session token is not found or is expired, the returned exists flag will +// be set to false. +func (p *LibsqlStore) Find(token string) (b []byte, exists bool, err error) { + row := p.db.QueryRow("SELECT data FROM sessions WHERE token = ? AND julianday('now') < expiry", token) + err = row.Scan(&b) + if err == sql.ErrNoRows { + return nil, false, nil + } else if err != nil { + return nil, false, err + } + return b, true, nil +} + +// Commit adds a session token and data to the LibsqlStore instance with the +// given expiry time. If the session token already exists, then the data and expiry +// time are updated. +func (p *LibsqlStore) Commit(token string, b []byte, expiry time.Time) error { + _, err := p.db.Exec("REPLACE INTO sessions (token, data, expiry) VALUES (?, ?, julianday(?))", token, b, expiry.UTC().Format("2006-01-02T15:04:05.999")) + if err != nil { + return err + } + return nil +} + +// Delete removes a session token and corresponding data from the LibsqlStore +// instance. +func (p *LibsqlStore) Delete(token string) error { + _, err := p.db.Exec("DELETE FROM sessions WHERE token = ?", token) + return err +} + +// All returns a map containing the token and data for all active (i.e. +// not expired) sessions in the LibsqlStore instance. +func (p *LibsqlStore) All() (map[string][]byte, error) { + rows, err := p.db.Query("SELECT token, data FROM sessions WHERE julianday('now') < expiry") + if err != nil { + return nil, err + } + defer rows.Close() + + sessions := make(map[string][]byte) + + for rows.Next() { + var ( + token string + data []byte + ) + + err = rows.Scan(&token, &data) + if err != nil { + return nil, err + } + + sessions[token] = data + } + + err = rows.Err() + if err != nil { + return nil, err + } + + return sessions, nil +} + +func (p *LibsqlStore) startCleanup(interval time.Duration) { + p.stopCleanup = make(chan bool) + ticker := time.NewTicker(interval) + for { + select { + case <-ticker.C: + err := p.deleteExpired() + if err != nil { + log.Println(err) + } + case <-p.stopCleanup: + ticker.Stop() + return + } + } +} + +// StopCleanup terminates the background cleanup goroutine for the LibsqlStore +// instance. It's rare to terminate this; generally LibsqlStore instances and +// their cleanup goroutines are intended to be long-lived and run for the lifetime +// of your application. +// +// There may be occasions though when your use of the LibsqlStore is transient. +// An example is creating a new LibsqlStore instance in a test function. In this +// scenario, the cleanup goroutine (which will run forever) will prevent the +// LibsqlStore object from being garbage collected even after the test function +// has finished. You can prevent this by manually calling StopCleanup. +func (p *LibsqlStore) StopCleanup() { + if p.stopCleanup != nil { + p.stopCleanup <- true + } +} + +func (p *LibsqlStore) deleteExpired() error { + _, err := p.db.Exec("DELETE FROM sessions WHERE expiry < julianday('now')") + return err +} diff --git a/libsqlstore/libsqlstore_test.go b/libsqlstore/libsqlstore_test.go new file mode 100644 index 0000000..5474dc7 --- /dev/null +++ b/libsqlstore/libsqlstore_test.go @@ -0,0 +1,320 @@ +package libsqlstore + +import ( + "bytes" + "database/sql" + "fmt" + "os" + "reflect" + "testing" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +func createDBwithSessionTable(db *sql.DB) error { + q := `CREATE TABLE sessions ( + token TEXT PRIMARY KEY, + data BLOB NOT NULL, + expiry REAL NOT NULL + ); + CREATE INDEX sessions_expiry_idx ON sessions(expiry);` + _, err := db.Exec(q) + if err != nil { + return err + } + return nil +} + +func removeDBfile(dsn string) error { + fileinfo, _ := os.Stat(dsn) + if fileinfo != nil { + err := os.Remove(dsn) + if err != err { + return err + } + } + return nil +} + +func TestFind(t *testing.T) { + dsn := "./testSQL3lite.db" + + if err := removeDBfile(dsn); err != nil { + t.Fatal(err) + } + + db, err := sql.Open("sqlite3", dsn) + if err != nil { + t.Fatal(err) + } + + defer os.Remove(dsn) + defer db.Close() + + if err := createDBwithSessionTable(db); err != nil { + t.Fatal(err) + } + if err = db.Ping(); err != nil { + t.Fatal(err) + } + _, err = db.Exec("DELETE FROM sessions") + if err != nil { + t.Fatal(err) + } + _, err = db.Exec("INSERT INTO sessions VALUES('session_token', 'encoded_data', datetime(current_timestamp, '+1 minute'))") + if err != nil { + t.Fatal(err) + } + + p := NewWithCleanupInterval(db, 0) + + b, found, err := p.Find("session_token") + if err != nil { + t.Fatal(err) + } + if found != true { + t.Fatalf("got %v: expected %v", found, true) + } + if bytes.Equal(b, []byte("encoded_data")) == false { + t.Fatalf("got %v: expected %v", b, []byte("encoded_data")) + } +} +func TestFindMissing(t *testing.T) { + dsn := "./testSQL3lite.db" + if err := removeDBfile(dsn); err != nil { + t.Fatal(err) + } + db, err := sql.Open("sqlite3", dsn) + if err != nil { + t.Fatal(err) + } + defer os.Remove(dsn) + defer db.Close() + + if err := createDBwithSessionTable(db); err != nil { + t.Fatal(err) + } + if err = db.Ping(); err != nil { + t.Fatal(err) + } + _, err = db.Exec("DELETE FROM sessions") + if err != nil { + t.Fatal(err) + } + _, err = db.Exec("INSERT INTO sessions VALUES('session_token', 'encoded_data', datetime(current_timestamp, '+1 minute'))") + if err != nil { + t.Fatal(err) + } + + p := NewWithCleanupInterval(db, 0) + + _, found, err := p.Find("missing_session_token") + if err != nil { + t.Fatalf("got %v: expected %v", err, nil) + } + if found != false { + t.Fatalf("got %v: expected %v", found, false) + } +} + +func TestSaveNew(t *testing.T) { + dsn := "./testSQL3lite.db" + if err := removeDBfile(dsn); err != nil { + t.Fatal(err) + } + db, err := sql.Open("sqlite3", dsn) + if err != nil { + t.Fatal(err) + } + defer os.Remove(dsn) + defer db.Close() + + if err := createDBwithSessionTable(db); err != nil { + t.Fatal(err) + } + if err = db.Ping(); err != nil { + t.Fatal(err) + } + _, err = db.Exec("DELETE FROM sessions") + if err != nil { + t.Fatal(err) + } + + p := NewWithCleanupInterval(db, 0) + + err = p.Commit("session_token", []byte("encoded_data"), time.Now().Add(time.Minute)) + if err != nil { + t.Fatal(err) + } + + row := db.QueryRow("SELECT data FROM sessions WHERE token = 'session_token'") + var data []byte + err = row.Scan(&data) + if err != nil { + t.Fatal(err) + } + if reflect.DeepEqual(data, []byte("encoded_data")) == false { + t.Fatalf("got %v: expected %v", data, []byte("encoded_data")) + } +} + +func TestSaveUpdated(t *testing.T) { + dsn := "./testSQL3lite.db" + if err := removeDBfile(dsn); err != nil { + t.Fatal(err) + } + db, err := sql.Open("sqlite3", dsn) + if err != nil { + t.Fatal(err) + } + defer os.Remove(dsn) + defer db.Close() + + if err = db.Ping(); err != nil { + t.Fatal(err) + } + + if err := createDBwithSessionTable(db); err != nil { + t.Fatal(err) + } + + _, err = db.Exec("DELETE FROM sessions") + if err != nil { + t.Fatal(err) + } + _, err = db.Exec("INSERT INTO sessions VALUES('session_token', 'encoded_data', datetime(current_timestamp, '+1 minute'))") + if err != nil { + t.Fatal(err) + } + + p := NewWithCleanupInterval(db, 0) + + err = p.Commit("session_token", []byte("new_encoded_data"), time.Now().Add(time.Minute)) + if err != nil { + t.Fatal(err) + } + + row := db.QueryRow("SELECT data FROM sessions WHERE token = 'session_token'") + var data []byte + err = row.Scan(&data) + if err != nil { + t.Fatal(err) + } + if reflect.DeepEqual(data, []byte("new_encoded_data")) == false { + t.Fatalf("got %v: expected %v", data, []byte("new_encoded_data")) + } +} + +func TestExpiry(t *testing.T) { + dsn := "./testSQL3lite.db" + if err := removeDBfile(dsn); err != nil { + t.Fatal(err) + } + db, err := sql.Open("sqlite3", dsn) + if err != nil { + t.Fatal(err) + } + defer db.Close() + defer os.Remove(dsn) + + if err := createDBwithSessionTable(db); err != nil { + t.Fatal(err) + } + if err = db.Ping(); err != nil { + t.Fatal(err) + } + _, err = db.Exec("DELETE FROM sessions") + if err != nil { + t.Fatal(err) + } + + p := NewWithCleanupInterval(db, 0) + fmt.Print() + err = p.Commit("session_token", []byte("encoded_data"), time.Now().Add(100*time.Millisecond)) + if err != nil { + t.Fatal(err) + } + + _, found, _ := p.Find("session_token") + if found != true { + t.Fatalf("got %v: expected %v", found, true) + } + + time.Sleep(100 * time.Millisecond) + _, found, _ = p.Find("session_token") + if found != false { + t.Fatalf("got %v: expected %v", found, false) + } +} + +func TestCleanup(t *testing.T) { + dsn := "./testSQL3lite.db" + if err := removeDBfile(dsn); err != nil { + t.Fatal(err) + } + db, err := sql.Open("sqlite3", dsn) + if err != nil { + t.Fatal(err) + } + defer db.Close() + defer os.Remove(dsn) + + if err = db.Ping(); err != nil { + t.Fatal(err) + } + + if err := createDBwithSessionTable(db); err != nil { + t.Fatal(err) + } + + p := NewWithCleanupInterval(db, 200*time.Millisecond) + defer p.StopCleanup() + + err = p.Commit("session_token", []byte("encoded_data"), time.Now().Add(100*time.Millisecond)) + if err != nil { + t.Fatal(err) + } + + row := db.QueryRow("SELECT COUNT(*) FROM sessions WHERE token = 'session_token'") + var count int + err = row.Scan(&count) + if err != nil { + t.Fatal(err) + } + if count != 1 { + t.Fatalf("got %d: expected %d", count, 1) + } + + time.Sleep(300 * time.Millisecond) + row = db.QueryRow("SELECT COUNT(*) FROM sessions WHERE token = 'session_token'") + err = row.Scan(&count) + if err != nil { + t.Fatal(err) + } + if count != 0 { + t.Fatalf("got %d: expected %d", count, 0) + } +} + +func TestStopNilCleanup(t *testing.T) { + dsn := "./testSQL3lite.db" + if err := removeDBfile(dsn); err != nil { + t.Fatal(err) + } + db, err := sql.Open("sqlite3", dsn) + if err != nil { + t.Fatal(err) + } + defer db.Close() + defer os.Remove(dsn) + + if err = db.Ping(); err != nil { + t.Fatal(err) + } + + p := NewWithCleanupInterval(db, 0) + time.Sleep(100 * time.Millisecond) + // A send to a nil channel will block forever + p.StopCleanup() +}