diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5c3123f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,18 @@ +name: build +on: [push, pull_request] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ankane/setup-postgres@v1 + with: + database: pgvector_c_test + - run: | + cd /tmp + git clone --branch v0.5.1 https://github.com/pgvector/pgvector.git + cd pgvector + make + sudo make install + - run: gcc -Wall -Wextra -Werror -o test/pq test/pq_test.cpp -lpq + - run: test/pq diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec58228 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/test/pq diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..0e5e609 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 Andrew Kane + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..33ce159 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# pgvector-c + +[pgvector](https://github.com/pgvector/pgvector) examples for C + +Supports [libpq](https://www.postgresql.org/docs/current/libpq.html) + +[![Build Status](https://github.com/pgvector/pgvector-c/workflows/build/badge.svg?branch=master)](https://github.com/pgvector/pgvector-c/actions) + +## Getting Started + +Follow the instructions for your database library: + +- [libpq](#libpq) + +## libpq + +Enable the extension + +```c +PGresult *res = PQexec(conn, "CREATE EXTENSION IF NOT EXISTS vector"); +``` + +Create a table + +```c +PGresult *res = PQexec(conn, "CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))"); +``` + +Insert vectors + +```c +const char *paramValues[2] = {"[1,2,3]", "[4,5,6]"}; +PGresult *res = PQexecParams(conn, "INSERT INTO items (embedding) VALUES ($1), ($2)", 2, NULL, paramValues, NULL, NULL, 0); +``` + +Get the nearest neighbors + +```c +const char *paramValues[1] = {"[3,1,2]"}; +PGresult *res = PQexecParams(conn, "SELECT * FROM items ORDER BY embedding <-> $1 LIMIT 5", 1, NULL, paramValues, NULL, NULL, 0); +``` + +See a [full example](test/pq_test.c) + +## Contributing + +Everyone is encouraged to help improve this project. Here are a few ways you can help: + +- [Report bugs](https://github.com/pgvector/pgvector-c/issues) +- Fix bugs and [submit pull requests](https://github.com/pgvector/pgvector-c/pulls) +- Write, clarify, or fix documentation +- Suggest or add new features + +To get started with development: + +```sh +git clone https://github.com/pgvector/pgvector-c.git +cd pgvector-c +createdb pgvector_c_test +gcc -Wall -Wextra -Werror -o test/pq test/pq_test.c -lpq +test/pq +``` diff --git a/test/pq_test.c b/test/pq_test.c new file mode 100644 index 0000000..e441d59 --- /dev/null +++ b/test/pq_test.c @@ -0,0 +1,40 @@ +#include +#include + +int main() { + PGconn *conn; + PGresult *res; + + conn = PQconnectdb("postgres://localhost/pgvector_c_test"); + assert(PQstatus(conn) == CONNECTION_OK); + + res = PQexec(conn, "CREATE EXTENSION IF NOT EXISTS vector"); + assert(PQresultStatus(res) == PGRES_COMMAND_OK); + PQclear(res); + + res = PQexec(conn, "DROP TABLE IF EXISTS items"); + assert(PQresultStatus(res) == PGRES_COMMAND_OK); + PQclear(res); + + res = PQexec(conn, "CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))"); + assert(PQresultStatus(res) == PGRES_COMMAND_OK); + PQclear(res); + + const char *paramValues[3] = {"[1,1,1]", "[2,2,2]", "[1,1,2]"}; + res = PQexecParams(conn, "INSERT INTO items (embedding) VALUES ($1), ($2), ($3)", 3, NULL, paramValues, NULL, NULL, 0); + assert(PQresultStatus(res) == PGRES_COMMAND_OK); + PQclear(res); + + const char *paramValues2[1] = {"[1,1,1]"}; + res = PQexecParams(conn, "SELECT * FROM items ORDER BY embedding <-> $1 LIMIT 5", 1, NULL, paramValues2, NULL, NULL, 0); + assert(PQresultStatus(res) == PGRES_TUPLES_OK); + int ntuples = PQntuples(res); + for (int i = 0; i < ntuples; i++) { + printf("%s: %s\n", PQgetvalue(res, i, 0), PQgetvalue(res, i, 1)); + } + PQclear(res); + + PQfinish(conn); + + return 0; +}