Skip to content

Instantly share code, notes, and snippets.

@paulfitz
Forked from mhodgson/postgres_odb_backend.c
Last active August 29, 2015 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulfitz/3e6bb6367b808293fc63 to your computer and use it in GitHub Desktop.
Save paulfitz/3e6bb6367b808293fc63 to your computer and use it in GitHub Desktop.
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
#include <assert.h>
#include <string.h>
#include <git2.h>
#include <git2/odb.h>
#include <git2/sys/odb_backend.h>
#include <buffer.h>
#include <odb.h>
#include <libpq-fe.h>
#define GIT2_ODB_TABLE_NAME "git_objects"
typedef struct {
git_odb_backend parent;
PGconn *db;
char *repo_name;
} postgres_odb_backend;
void postgres_odb_exception_raise(void)
{
VALUE err_obj, rb_mRugged, rb_eRepoError;
const git_error *error;
const char *err_message;
error = giterr_last();
if (error && error->message) {
err_message = error->message;
} else {
err_message = "Unknown Error";
}
rb_mRugged = rb_const_get(rb_cObject, rb_intern("Rugged"));
rb_eRepoError = rb_const_get(rb_mRugged, rb_intern("RepositoryError"));
err_obj = rb_exc_new2(rb_eRepoError, err_message);
giterr_clear();
rb_exc_raise(err_obj);
}
static inline void postgres_odb_exception_check(int errorcode)
{
if (errorcode < 0)
postgres_odb_exception_raise();
}
static int postgres_odb_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid)
{
postgres_odb_backend *backend = (postgres_odb_backend *)_backend;
PGresult *result;
const char* paramValues[2];
int paramLengths[2];
int binary[2] = {0, 1};
int error = GIT_ERROR;
assert(len_p && type_p && backend && oid);
paramValues[0] = (char *)backend->repo_name;
paramValues[1] = (char *)oid->id;
paramLengths[0] = strlen(backend->repo_name);
paramLengths[1] = 20;
result = PQexecPrepared(backend->db, "git_odb_read_header", 2, paramValues, paramLengths, binary, 1);
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
giterr_set(GITERR_INVALID, "Error reading object in Postgres ODB backend: %s", PQerrorMessage(backend->db));
postgres_odb_exception_check(GIT_ERROR);
error = GIT_ERROR;
} else {
if (PQntuples(result) == 0) {
error = git_odb__error_notfound("Could not find object in Postgres ODB backend", oid);
postgres_odb_exception_check(error);
} else {
*type_p = (git_otype)ntohl(*((uint32_t *)PQgetvalue(result, 0, 0)));
*len_p = (size_t)ntohl(*((uint32_t *)PQgetvalue(result, 0, 1)));
error = GIT_OK;
}
}
PQclear(result);
return error;
}
static int postgres_odb_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid)
{
postgres_odb_backend *backend = (postgres_odb_backend *)_backend;
PGresult *result;
const char* paramValues[2];
int paramLengths[2];
int binary[2] = {0, 1};
int error = GIT_ERROR;
assert(data_p && len_p && type_p && backend && oid);
paramValues[0] = (char *)backend->repo_name;
paramValues[1] = (char *)oid->id;
paramLengths[0] = strlen(backend->repo_name);
paramLengths[1] = 20;
result = PQexecPrepared(backend->db, "git_odb_read", 2, paramValues, paramLengths, binary, 1);
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
giterr_set(GITERR_INVALID, "Error reading object in Postgres ODB backend: %s", PQerrorMessage(backend->db));
postgres_odb_exception_check(GIT_ERROR);
error = GIT_ERROR;
} else {
if (PQntuples(result) == 0) {
error = git_odb__error_notfound("Could not find object in Postgres ODB backend", oid);
postgres_odb_exception_check(error);
} else {
*type_p = (git_otype)ntohl(*((uint32_t *)PQgetvalue(result, 0, 0)));
*len_p = (size_t)ntohl(*((uint32_t *)PQgetvalue(result, 0, 1)));
*data_p = malloc(*len_p);
if (*data_p == NULL) {
error = GITERR_NOMEMORY;
} else {
memcpy(*data_p, PQgetvalue(result, 0, 2), *len_p);
error = GIT_OK;
}
}
}
PQclear(result);
return error;
}
static int postgres_odb_backend__read_prefix(
git_oid *out_oid,
void **data_p,
size_t *len_p,
git_otype *type_p,
git_odb_backend *_backend,
const git_oid *short_oid,
size_t len)
{
if (len >= GIT_OID_HEXSZ) {
/* Just match the full identifier */
int error = postgres_odb_backend__read(data_p, len_p, type_p, _backend, short_oid);
if (error == GIT_OK)
git_oid_cpy(out_oid, short_oid);
return error;
} else if (len < GIT_OID_HEXSZ) {
return git_odb__error_ambiguous("prefix length too short");
} else {
return 0;
}
}
static int postgres_odb_backend__exists(git_odb_backend *_backend, const git_oid *oid)
{
postgres_odb_backend *backend = (postgres_odb_backend *)_backend;
PGresult *result;
const char* paramValues[2];
int paramLengths[2];
int binary[2] = {0, 1};
int found = 0;
assert(backend && oid);
paramValues[0] = (char *)backend->repo_name;
paramValues[1] = (char *)oid->id;
paramLengths[0] = strlen(backend->repo_name);
paramLengths[1] = 20;
result = PQexecPrepared(backend->db, "git_odb_read_header", 2, paramValues, paramLengths, binary, 0);
found = PQntuples(result);
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
giterr_set(GITERR_INVALID, "Error checking for existence of object in Postgres ODB backend: %s", PQerrorMessage(backend->db));
PQclear(result);
postgres_odb_exception_check(GIT_ERROR);
}
PQclear(result);
return found;
}
static int postgres_odb_backend__write(git_odb_backend *_backend, git_oid *id, const void *data, size_t len, git_otype type)
{
int error = 0;
postgres_odb_backend *backend = (postgres_odb_backend *)_backend;
PGresult *result;
const char *paramValues[5];
int paramLengths[5];
int binary[5] = {0, 1, 1, 1, 1};
int type_int = htonl((int)type);
int len_int = htonl(len);
assert(id && backend && data);
if ((error = git_odb_hash(id, data, len, type)) < 0)
return error;
paramValues[0] = (char *)backend->repo_name;
paramValues[1] = (char *)id->id;
paramValues[2] = (char *)&type_int;
paramValues[3] = (char *)&len_int;
paramValues[4] = data;
paramLengths[0] = strlen(backend->repo_name);
paramLengths[1] = 20;
paramLengths[2] = sizeof(type_int);
paramLengths[3] = sizeof(len_int);
paramLengths[4] = len;
result = PQexecPrepared(backend->db, "git_odb_write", 5, paramValues, paramLengths, binary, 0);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_INVALID, "Error writing object to Postgres ODB backend: %s", PQerrorMessage(backend->db));
PQclear(result);
postgres_odb_exception_check(GIT_ERROR);
return GIT_ERROR;
}
PQclear(result);
return GIT_OK;
}
static void postgres_odb_backend__free(git_odb_backend *_backend)
{
postgres_odb_backend *backend = (postgres_odb_backend *)_backend;
assert(backend);
free(backend);
}
static int create_table(PGconn *db)
{
PGresult *result;
static const char *sql_create =
"CREATE TABLE " GIT2_ODB_TABLE_NAME " ("
"repo text NOT NULL,"
"oid bytea NOT NULL,"
"type integer NOT NULL,"
"size integer NOT NULL,"
"data bytea,"
"PRIMARY KEY (repo, oid));";
result = PQexec(db, sql_create);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
PQclear(result);
giterr_set(GITERR_ODB, "Error creating table for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
return GIT_OK;
}
static int init_db(PGconn *db)
{
git_buf sql_check = GIT_BUF_INIT;
PGresult *result;
int error = GIT_OK;
git_buf_printf(&sql_check, "SELECT 1 FROM information_schema.tables WHERE table_catalog='%s' AND table_schema='public' AND table_name='%s';", PQdb(db), GIT2_ODB_TABLE_NAME);
result = PQexec(db, git_buf_cstr(&sql_check));
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
giterr_set(GITERR_ODB, "Error establishing existence of table for Postgres ODB backend: %s", PQerrorMessage(db));
error = GIT_ERROR;
} else {
if (PQntuples(result) == 0) {
error = create_table(db);
}
}
PQclear(result);
return error;
}
static int init_statements(PGconn *db)
{
PGresult *result;
static const char *sql_read =
"SELECT type, size, data FROM " GIT2_ODB_TABLE_NAME " WHERE repo = $1::text AND oid = $2::bytea;";
static const char *sql_read_header =
"SELECT type, size FROM " GIT2_ODB_TABLE_NAME " WHERE repo = $1::text AND oid = $2::bytea;";
static const char *sql_write =
"INSERT INTO " GIT2_ODB_TABLE_NAME " VALUES ($1::text, $2::bytea, $3::integer, $4::integer, $5::bytea);";
result = PQprepare(db, "git_odb_read", sql_read, 0, NULL);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
result = PQprepare(db, "git_odb_read_header", sql_read_header, 0, NULL);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
result = PQprepare(db, "git_odb_write", sql_write, 0, NULL);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
return GIT_OK;
}
void parse_conninfo_postgres(
char *out,
char *database,
char *username,
char *password,
char *hostname,
int port)
{
git_buf conninfo = GIT_BUF_INIT;
git_buf_printf(&conninfo, "client_encoding=UTF8 dbname=%s", database);
if (username)
git_buf_printf(&conninfo, " user=%s", username);
if (password)
git_buf_printf(&conninfo, " password=%s", password);
if (hostname) {
if (strcmp(hostname, "localhost") == 0) {
git_buf_printf(&conninfo, " hostaddr=%s", "127.0.0.1");
} else {
git_buf_printf(&conninfo, " hostaddr=%s", hostname);
}
}
if (port > 0)
git_buf_printf(&conninfo, " port=%d", port);
git_buf_copy_cstr(out, git_buf_len(&conninfo) + 1, &conninfo);
git_buf_free(&conninfo);
}
int prep_odb(PGconn *conn)
{
int error = GIT_OK;
error = init_db(conn);
error = init_statements(conn);
return error;
}
int git_odb_backend_postgres(
git_odb_backend **backend_out,
PGconn *conn,
char *repo_name)
{
postgres_odb_backend *backend;
backend = calloc(1, sizeof(postgres_odb_backend));
if (backend == NULL)
return -1;
backend->parent.version = GIT_ODB_BACKEND_VERSION;
backend->db = conn;
backend->repo_name = repo_name;
backend->parent.read = &postgres_odb_backend__read;
backend->parent.write = &postgres_odb_backend__write;
backend->parent.read_prefix = &postgres_odb_backend__read_prefix;
backend->parent.read_header = &postgres_odb_backend__read_header;
backend->parent.exists = &postgres_odb_backend__exists;
backend->parent.free = &postgres_odb_backend__free;
*backend_out = (git_odb_backend *)backend;
return 0;
}
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
#include <assert.h>
#include <string.h>
#include <git2.h>
#include <git2/tag.h>
#include <git2/buffer.h>
#include <git2/object.h>
#include <git2/refdb.h>
#include <git2/errors.h>
#include <git2/sys/refdb_backend.h>
#include <git2/sys/refs.h>
#include <git2/sys/reflog.h>
#include <refs.h>
#include <iterator.h>
#include <refdb.h>
#include <fnmatch.h>
#include <pool.h>
#include <buffer.h>
#include <libpq-fe.h>
#define GIT2_REFDB_TABLE_NAME "git_refs"
#define GIT_SYMREF "ref: "
typedef struct postgres_refdb_backend {
git_refdb_backend parent;
git_repository *repo;
PGconn *db;
char *repo_name;
} postgres_refdb_backend;
void postgres_refdb_exception_raise(void)
{
VALUE err_obj, rb_mRugged, rb_eRepoError;
const git_error *error;
const char *err_message;
error = giterr_last();
if (error && error->message) {
err_message = error->message;
} else {
err_message = "Unknown Error";
}
rb_mRugged = rb_const_get(rb_cObject, rb_intern("Rugged"));
rb_eRepoError = rb_const_get(rb_mRugged, rb_intern("RepositoryError"));
err_obj = rb_exc_new2(rb_eRepoError, err_message);
giterr_clear();
rb_exc_raise(err_obj);
}
static inline void postgres_refdb_exception_check(int errorcode)
{
if (errorcode < 0)
postgres_refdb_exception_raise();
}
static int ref_error_notfound(const char *name)
{
giterr_set(GITERR_REFERENCE, "Reference not found: %s", name);
return GIT_ENOTFOUND;
}
static const char *parse_symbolic(git_buf *ref_content)
{
const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
const char *refname_start;
refname_start = (const char *)git_buf_cstr(ref_content);
if (git_buf_len(ref_content) < header_len + 1) {
giterr_set(GITERR_REFERENCE, "Corrupted reference");
return NULL;
}
/*
* Assume we have already checked for the header
* before calling this function
*/
refname_start += header_len;
return refname_start;
}
static int parse_oid(git_oid *oid, const char *filename, git_buf *ref_content)
{
const char *str = git_buf_cstr(ref_content);
if (git_buf_len(ref_content) < GIT_OID_HEXSZ)
goto corrupted;
/* we need to get 40 OID characters from the file */
if (git_oid_fromstr(oid, str) < 0)
goto corrupted;
/* If the file is longer than 40 chars, the 41st must be a space */
str += GIT_OID_HEXSZ;
if (*str == '\0' || git__isspace(*str))
return 0;
corrupted:
giterr_set(GITERR_REFERENCE, "Corrupted reference");
return -1;
}
static int postgres_refdb_backend__exists(
int *exists,
git_refdb_backend *_backend,
const char *ref_name)
{
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend;
PGresult *result;
const char* paramValues[2];
int paramLengths[2];
int binary[2] = {0, 0};
assert(backend);
*exists = 0;
paramValues[0] = (char *)backend->repo_name;
paramValues[1] = ref_name;
paramLengths[0] = strlen(backend->repo_name);
paramLengths[1] = strlen(ref_name);
result = PQexecPrepared(backend->db, "git_refdb_read", 2, paramValues, paramLengths, binary, 0);
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
giterr_set(GITERR_INVALID, "Error checking for reference in Postgres RefDB backend: %s", PQerrorMessage(backend->db));
postgres_refdb_exception_check(GIT_ERROR);
} else {
if (PQntuples(result) > 0) {
*exists = 1;
}
}
PQclear(result);
return 0;
}
static int loose_lookup(
git_reference **out,
postgres_refdb_backend *backend,
const char *ref_name)
{
git_buf ref_buf = GIT_BUF_INIT;
PGresult *result;
const char* paramValues[2];
int paramLengths[2];
int binary[2] = {0, 0};
int error = 0;
paramValues[0] = (char *)backend->repo_name;
paramValues[1] = ref_name;
paramLengths[0] = strlen(backend->repo_name);
paramLengths[1] = strlen(ref_name);
result = PQexecPrepared(backend->db, "git_refdb_read", 2, paramValues, paramLengths, binary, 0);
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
giterr_set(GITERR_INVALID, "Error reading reference in Postgres RefDB backend: %s", PQerrorMessage(backend->db));
postgres_refdb_exception_check(GIT_ERROR);
error = GIT_ERROR;
} else {
if (PQntuples(result) == 0) {
error = ref_error_notfound(ref_name);
} else {
char *raw_ref = (char *)PQgetvalue(result, 0, 0);
git_buf_set(&ref_buf, raw_ref, strlen(raw_ref));
if (git__prefixcmp(git_buf_cstr(&ref_buf), GIT_SYMREF) == 0) {
const char *target;
git_buf_rtrim(&ref_buf);
if (!(target = parse_symbolic(&ref_buf)))
error = -1;
else if (out != NULL)
*out = git_reference__alloc_symbolic(ref_name, target);
} else {
git_oid oid;
if (!(error = parse_oid(&oid, ref_name, &ref_buf)) && out != NULL)
*out = git_reference__alloc(ref_name, &oid, NULL);
}
}
}
git_buf_free(&ref_buf);
PQclear(result);
return error;
}
static int postgres_refdb_backend__lookup(
git_reference **out,
git_refdb_backend *_backend,
const char *ref_name)
{
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend;
assert(backend);
return loose_lookup(out, backend, ref_name);
}
typedef struct {
git_reference_iterator parent;
char *glob;
git_pool pool;
git_vector loose;
size_t loose_pos;
} postgres_refdb_iter;
static void postgres_refdb_backend__iterator_free(git_reference_iterator *_iter)
{
postgres_refdb_iter *iter = (postgres_refdb_iter *) _iter;
git_vector_free(&iter->loose);
git_pool_clear(&iter->pool);
git__free(iter);
}
static int iter_load_loose_paths(postgres_refdb_backend *backend, postgres_refdb_iter *iter)
{
PGresult *result;
const char *paramValues[1];
int paramLengths[1];
int binary[1] = {0};
int error = GIT_OK;
paramValues[0] = backend->repo_name;
paramLengths[0] = strlen(backend->repo_name);
result = PQexecPrepared(backend->db, "git_refdb_read_all", 1, paramValues, paramLengths, binary, 0);
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
giterr_set(GITERR_ODB, "Error reading references from Postgres RefDB backend: %s", PQerrorMessage(backend->db));
error = GIT_ERROR;
postgres_refdb_exception_check(error);
} else {
int i;
for (i = 0; i < PQntuples(result); i++) {
char *ref_dup;
char *ref_name = PQgetvalue(result, i, 0);
if (git__prefixcmp(ref_name, GIT_REFS_DIR) != 0)
continue;
if (git__suffixcmp(ref_name, ".lock") == 0 ||
(iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0))
continue;
ref_dup = git_pool_strdup(&iter->pool, ref_name);
if (!ref_dup)
error = -1;
else
error = git_vector_insert(&iter->loose, ref_dup);
}
}
PQclear(result);
return error;
}
static int postgres_refdb_backend__iterator_next(
git_reference **out, git_reference_iterator *_iter)
{
int error = GIT_ITEROVER;
postgres_refdb_iter *iter = (postgres_refdb_iter *)_iter;
postgres_refdb_backend *backend = (postgres_refdb_backend *)iter->parent.db->backend;
while (iter->loose_pos < iter->loose.length) {
const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
if (loose_lookup(out, backend, path) == 0)
return 0;
giterr_clear();
}
return error;
}
static int postgres_refdb_backend__iterator_next_name(
const char **out, git_reference_iterator *_iter)
{
int error = GIT_ITEROVER;
postgres_refdb_iter *iter = (postgres_refdb_iter *)_iter;
postgres_refdb_backend *backend = (postgres_refdb_backend *)iter->parent.db->backend;
while (iter->loose_pos < iter->loose.length) {
const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
if (loose_lookup(NULL, backend, path) == 0) {
*out = path;
return 0;
}
giterr_clear();
}
return error;
}
static int postgres_refdb_backend__iterator(
git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
{
postgres_refdb_iter *iter;
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend;
assert(backend);
iter = git__calloc(1, sizeof(postgres_refdb_iter));
GITERR_CHECK_ALLOC(iter);
if (git_pool_init(&iter->pool, 1, 0) < 0 ||
git_vector_init(&iter->loose, 8, NULL) < 0)
goto fail;
if (glob != NULL &&
(iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL)
goto fail;
iter->parent.next = postgres_refdb_backend__iterator_next;
iter->parent.next_name = postgres_refdb_backend__iterator_next_name;
iter->parent.free = postgres_refdb_backend__iterator_free;
if (iter_load_loose_paths(backend, iter) < 0)
goto fail;
*out = (git_reference_iterator *)iter;
return 0;
fail:
postgres_refdb_backend__iterator_free((git_reference_iterator *)iter);
return -1;
}
static int reference_path_available(
postgres_refdb_backend *backend,
const char *new_ref,
const char* old_ref,
int force)
{
if (!force) {
int exists;
if (postgres_refdb_backend__exists(&exists, (git_refdb_backend *)backend, new_ref) < 0)
return -1;
if (exists) {
giterr_set(GITERR_REFERENCE,
"Failed to write reference '%s': a reference with "
"that name already exists.", new_ref);
return GIT_EEXISTS;
}
}
return 0;
}
static int postgres_refdb_backend__write(
git_refdb_backend *_backend,
const git_reference *ref,
int force,
const git_signature *who,
const char *message)
{
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend;
PGresult *result;
const char *paramValues[3];
int paramLengths[3];
int binary[3] = {0, 0, 0};
int error;
assert(backend);
error = reference_path_available(backend, ref->name, NULL, force);
if (error < 0)
return error;
paramValues[0] = backend->repo_name;
paramValues[1] = ref->name;
paramLengths[0] = strlen(backend->repo_name);
paramLengths[1] = strlen(ref->name);
if (ref->type == GIT_REF_OID) {
char oid[GIT_OID_HEXSZ + 1];
git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
paramValues[2] = (char *)oid;
paramLengths[2] = strlen((char *)oid);
} else if (ref->type == GIT_REF_SYMBOLIC) {
char *symbolic_ref = malloc(strlen(GIT_SYMREF)+strlen(ref->target.symbolic)+1);
strcpy(symbolic_ref, GIT_SYMREF);
strcat(symbolic_ref, ref->target.symbolic);
paramValues[2] = (char *)symbolic_ref;
paramLengths[2] = strlen((char *)symbolic_ref);
}
// Kinda sucks that we have to do a lookup for each write. Would be better to detect the unique key constraint error and attempt an update instead.
if (postgres_refdb_backend__lookup(NULL, _backend, ref->name) < 0) {
result = PQexecPrepared(backend->db, "git_refdb_write", 3, paramValues, paramLengths, binary, 0);
} else {
result = PQexecPrepared(backend->db, "git_refdb_update", 3, paramValues, paramLengths, binary, 0);
}
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error writing reference to Postgres RefDB backend: %s", PQerrorMessage(backend->db));
error = GIT_ERROR;
postgres_refdb_exception_check(error);
} else {
error = GIT_OK;
}
PQclear(result);
return error;
}
static int postgres_refdb_backend__delete(git_refdb_backend *_backend, const char *name)
{
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend;
PGresult *result;
const char *paramValues[2];
int paramLengths[2];
int binary[2] = {0, 0};
int error;
assert(backend && name);
paramValues[0] = backend->repo_name;
paramValues[1] = name;
paramLengths[0] = strlen(backend->repo_name);
paramLengths[1] = strlen(name);
result = PQexecPrepared(backend->db, "git_refdb_delete", 2, paramValues, paramLengths, binary, 0);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error deleting reference in Postgres RefDB backend: %s", PQerrorMessage(backend->db));
error = GIT_ERROR;
postgres_refdb_exception_check(error);
} else {
error = GIT_OK;
}
PQclear(result);
return error;
}
static int postgres_refdb_backend__rename(
git_reference **out,
git_refdb_backend *_backend,
const char *old_name,
const char *new_name,
int force,
const git_signature *who,
const char *message)
{
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend;
git_reference *old, *new;
int error;
assert(backend);
if ((error = reference_path_available(
backend, new_name, old_name, force)) < 0 ||
(error = postgres_refdb_backend__lookup(&old, _backend, old_name)) < 0)
return error;
if ((error = postgres_refdb_backend__delete(_backend, old_name)) < 0) {
git_reference_free(old);
return error;
}
new = git_reference__set_name(old, new_name);
if (!new) {
git_reference_free(old);
return -1;
}
if ((error = postgres_refdb_backend__write(_backend, new, force, who, message)) > 0) {
git_reference_free(new);
return error;
}
*out = new;
return GIT_OK;
}
static int postgres_refdb_backend__compress(git_refdb_backend *_backend)
{
return 0;
}
static int postgres_refdb_backend__has_log(git_refdb_backend *_backend, const char *name)
{
return -1;
}
static int postgres_refdb_backend__ensure_log(git_refdb_backend *_backend, const char *name)
{
return 0;
}
static void postgres_refdb_backend__free(git_refdb_backend *_backend)
{
postgres_refdb_backend *backend = (postgres_refdb_backend *)_backend;
assert(backend);
free(backend);
}
static int postgres_refdb_backend__reflog_read(git_reflog **out, git_refdb_backend *_backend, const char *name)
{
return 0;
}
static int postgres_refdb_backend__reflog_write(git_refdb_backend *_backend, git_reflog *reflog)
{
return 0;
}
static int postgres_refdb_backend__reflog_rename(git_refdb_backend *_backend, const char *old_name, const char *new_name)
{
return 0;
}
static int postgres_refdb_backend__reflog_delete(git_refdb_backend *_backend, const char *name)
{
return 0;
}
static int create_table(PGconn *db)
{
PGresult *result;
static const char *sql_create =
"CREATE TABLE " GIT2_REFDB_TABLE_NAME " ("
"repo text NOT NULL,"
"refname text NOT NULL,"
"ref text NOT NULL,"
"PRIMARY KEY (repo, refname));";
result = PQexec(db, sql_create);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
PQclear(result);
giterr_set(GITERR_ODB, "Error creating table for Postgres RefDB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
return GIT_OK;
}
static int init_db(PGconn *db)
{
git_buf sql_check = GIT_BUF_INIT;
PGresult *result;
int error = GIT_OK;
git_buf_printf(&sql_check, "SELECT 1 FROM information_schema.tables WHERE table_catalog='%s' AND table_schema='public' AND table_name='%s';", PQdb(db), GIT2_REFDB_TABLE_NAME);
result = PQexec(db, git_buf_cstr(&sql_check));
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
giterr_set(GITERR_ODB, "Error establishing existence of table for Postgres RefDB backend: %s", PQerrorMessage(db));
error = GIT_ERROR;
} else {
if (PQntuples(result) == 0) {
error = create_table(db);
}
}
PQclear(result);
return error;
}
static int init_statements(PGconn *db)
{
PGresult *result;
static const char *sql_read =
"SELECT ref FROM " GIT2_REFDB_TABLE_NAME " WHERE repo = $1::text AND refname = $2::text;";
static const char *sql_read_all =
"SELECT refname FROM " GIT2_REFDB_TABLE_NAME " WHERE repo = $1::text;";
static const char *sql_write =
"INSERT INTO " GIT2_REFDB_TABLE_NAME " VALUES ($1::text, $2::text, $3::text);";
static const char *sql_update =
"UPDATE " GIT2_REFDB_TABLE_NAME " SET ref = $3::text WHERE repo = $1::text AND refname = $2::text;";
static const char *sql_delete =
"DELETE FROM " GIT2_REFDB_TABLE_NAME " WHERE repo = $1::text AND refname = $2::text;";
result = PQprepare(db, "git_refdb_read", sql_read, 0, NULL);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
result = PQprepare(db, "git_refdb_read_all", sql_read_all, 0, NULL);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
result = PQprepare(db, "git_refdb_write", sql_write, 0, NULL);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
result = PQprepare(db, "git_refdb_update", sql_update, 0, NULL);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
result = PQprepare(db, "git_refdb_delete", sql_delete, 0, NULL);
if (PQresultStatus(result) != PGRES_COMMAND_OK) {
giterr_set(GITERR_ODB, "Error creating prepared statement for Postgres ODB backend: %s", PQerrorMessage(db));
return GIT_ERROR;
}
PQclear(result);
return GIT_OK;
}
int prep_refdb(PGconn *conn)
{
int error = GIT_OK;
error = init_db(conn);
error = init_statements(conn);
return error;
}
int git_refdb_backend_postgres(
git_refdb_backend **backend_out,
git_repository *repository,
PGconn *conn,
char *repo_name)
{
postgres_refdb_backend *backend;
backend = calloc(1, sizeof(postgres_refdb_backend));
if (backend == NULL)
return -1;
backend->repo = repository;
backend->db = conn;
backend->repo_name = repo_name;
backend->parent.exists = &postgres_refdb_backend__exists;
backend->parent.lookup = &postgres_refdb_backend__lookup;
backend->parent.iterator = &postgres_refdb_backend__iterator;
backend->parent.write = &postgres_refdb_backend__write;
backend->parent.del = &postgres_refdb_backend__delete;
backend->parent.rename = &postgres_refdb_backend__rename;
backend->parent.compress = &postgres_refdb_backend__compress;
backend->parent.has_log = &postgres_refdb_backend__has_log;
backend->parent.ensure_log = &postgres_refdb_backend__ensure_log;
backend->parent.free = &postgres_refdb_backend__free;
backend->parent.reflog_read = &postgres_refdb_backend__reflog_read;
backend->parent.reflog_write = &postgres_refdb_backend__reflog_write;
backend->parent.reflog_rename = &postgres_refdb_backend__reflog_rename;
backend->parent.reflog_delete = &postgres_refdb_backend__reflog_delete;
*backend_out = (git_refdb_backend *)backend;
git_reference *head;
if (postgres_refdb_backend__lookup(&head, *backend_out, GIT_HEAD_FILE) < 0) {
head = git_reference__alloc_symbolic(GIT_HEAD_FILE, "refs/heads/master");
postgres_refdb_backend__write(*backend_out, head, 1, NULL, NULL);
}
git_reference_free(head);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment