Add example that query github repositories
parent
afb8145692
commit
79fb9332c1
|
@ -0,0 +1,24 @@
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE=extension.exe
|
||||||
|
EXT=sqlite3_mod_vtable.dll
|
||||||
|
RM=cmd /c del
|
||||||
|
LIBCURL=-lcurldll
|
||||||
|
LDFLAG=
|
||||||
|
else
|
||||||
|
EXE=extension
|
||||||
|
EXT=sqlite3_mod_vtable.so
|
||||||
|
RM=rm
|
||||||
|
LDFLAG=-fPIC
|
||||||
|
LIBCURL=-lcurl
|
||||||
|
endif
|
||||||
|
|
||||||
|
all : $(EXE) $(EXT)
|
||||||
|
|
||||||
|
$(EXE) : extension.go
|
||||||
|
go build $<
|
||||||
|
|
||||||
|
$(EXT) : sqlite3_mod_vtable.cc
|
||||||
|
g++ $(LDFLAG) -shared -o $@ $< -lsqlite3 $(LIBCURL)
|
||||||
|
|
||||||
|
clean :
|
||||||
|
@-$(RM) $(EXE) $(EXT)
|
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mattn/go-sqlite3"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
sql.Register("sqlite3_with_extensions",
|
||||||
|
&sqlite3.SQLiteDriver{
|
||||||
|
Extensions: []string{
|
||||||
|
"sqlite3_mod_vtable",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite3_with_extensions", ":memory:")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
db.Exec("create virtual table repo using github(id, full_name, description, html_url)")
|
||||||
|
|
||||||
|
rows, err := db.Query("select id, full_name, description, html_url from repo")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var id, full_name, description, html_url string
|
||||||
|
rows.Scan(&id, &full_name, &description, &html_url)
|
||||||
|
fmt.Printf("%s: %s\n\t%s\n\t%s\n\n", id, full_name, description, html_url)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,238 @@
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <sqlite3ext.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include "picojson.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# define EXPORT __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
# define EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SQLITE_EXTENSION_INIT1;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* data; // response data from server
|
||||||
|
size_t size; // response size of data
|
||||||
|
} MEMFILE;
|
||||||
|
|
||||||
|
MEMFILE*
|
||||||
|
memfopen() {
|
||||||
|
MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
|
||||||
|
if (mf) {
|
||||||
|
mf->data = NULL;
|
||||||
|
mf->size = 0;
|
||||||
|
}
|
||||||
|
return mf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
memfclose(MEMFILE* mf) {
|
||||||
|
if (mf->data) free(mf->data);
|
||||||
|
free(mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
|
||||||
|
MEMFILE* mf = (MEMFILE*) stream;
|
||||||
|
int block = size * nmemb;
|
||||||
|
if (!mf) return block; // through
|
||||||
|
if (!mf->data)
|
||||||
|
mf->data = (char*) malloc(block);
|
||||||
|
else
|
||||||
|
mf->data = (char*) realloc(mf->data, mf->size + block);
|
||||||
|
if (mf->data) {
|
||||||
|
memcpy(mf->data + mf->size, ptr, block);
|
||||||
|
mf->size += block;
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
char*
|
||||||
|
memfstrdup(MEMFILE* mf) {
|
||||||
|
char* buf;
|
||||||
|
if (mf->size == 0) return NULL;
|
||||||
|
buf = (char*) malloc(mf->size + 1);
|
||||||
|
memcpy(buf, mf->data, mf->size);
|
||||||
|
buf[mf->size] = 0;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_connect(sqlite3 *db, void *pAux, int argc, const char * const *argv, sqlite3_vtab **ppVTab, char **c) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "CREATE TABLE " << argv[0]
|
||||||
|
<< "(id int, full_name text, description text, html_url text)";
|
||||||
|
int rc = sqlite3_declare_vtab(db, ss.str().c_str());
|
||||||
|
*ppVTab = (sqlite3_vtab *) sqlite3_malloc(sizeof(sqlite3_vtab));
|
||||||
|
memset(*ppVTab, 0, sizeof(sqlite3_vtab));
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_create(sqlite3 *db, void *pAux, int argc, const char * const * argv, sqlite3_vtab **ppVTab, char **c) {
|
||||||
|
return my_connect(db, pAux, argc, argv, ppVTab, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int my_disconnect(sqlite3_vtab *pVTab) {
|
||||||
|
sqlite3_free(pVTab);
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_destroy(sqlite3_vtab *pVTab) {
|
||||||
|
sqlite3_free(pVTab);
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sqlite3_vtab_cursor base;
|
||||||
|
int index;
|
||||||
|
picojson::value* rows;
|
||||||
|
} cursor;
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) {
|
||||||
|
MEMFILE* mf;
|
||||||
|
CURL* curl;
|
||||||
|
char* json;
|
||||||
|
CURLcode res = CURLE_OK;
|
||||||
|
char error[CURL_ERROR_SIZE] = {0};
|
||||||
|
char* cert_file = getenv("SSL_CERT_FILE");
|
||||||
|
|
||||||
|
mf = memfopen();
|
||||||
|
curl = curl_easy_init();
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.29.0");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repositories");
|
||||||
|
if (cert_file)
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CAINFO, cert_file);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
|
||||||
|
res = curl_easy_perform(curl);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
std::cerr << error << std::endl;
|
||||||
|
return SQLITE_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
picojson::value* v = new picojson::value;
|
||||||
|
std::string err;
|
||||||
|
picojson::parse(*v, mf->data, mf->data + mf->size, &err);
|
||||||
|
memfclose(mf);
|
||||||
|
|
||||||
|
if (!err.empty()) {
|
||||||
|
delete v;
|
||||||
|
std::cerr << err << std::endl;
|
||||||
|
return SQLITE_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor *c = (cursor *)sqlite3_malloc(sizeof(cursor));
|
||||||
|
c->rows = v;
|
||||||
|
c->index = 0;
|
||||||
|
*ppCursor = &c->base;
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_close(cursor *c) {
|
||||||
|
delete c->rows;
|
||||||
|
sqlite3_free(c);
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_filter(cursor *c, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) {
|
||||||
|
c->index = 0;
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_next(cursor *c) {
|
||||||
|
c->index++;
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_eof(cursor *c) {
|
||||||
|
return c->index >= c->rows->get<picojson::array>().size() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_column(cursor *c, sqlite3_context *ctxt, int i) {
|
||||||
|
picojson::value v = c->rows->get<picojson::array>()[c->index];
|
||||||
|
picojson::object row = v.get<picojson::object>();
|
||||||
|
const char* p = NULL;
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
p = row["id"].to_str().c_str();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
p = row["full_name"].to_str().c_str();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
p = row["description"].to_str().c_str();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
p = row["html_url"].to_str().c_str();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sqlite3_result_text(ctxt, strdup(p), strlen(p), free);
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_rowid(cursor *c, sqlite3_int64 *pRowid) {
|
||||||
|
*pRowid = c->index;
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
my_bestindex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo) {
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const sqlite3_module module = {
|
||||||
|
0,
|
||||||
|
my_create,
|
||||||
|
my_connect,
|
||||||
|
my_bestindex,
|
||||||
|
my_disconnect,
|
||||||
|
my_destroy,
|
||||||
|
my_open,
|
||||||
|
(int (*)(sqlite3_vtab_cursor *)) my_close,
|
||||||
|
(int (*)(sqlite3_vtab_cursor *, int, char const *, int, sqlite3_value **)) my_filter,
|
||||||
|
(int (*)(sqlite3_vtab_cursor *)) my_next,
|
||||||
|
(int (*)(sqlite3_vtab_cursor *)) my_eof,
|
||||||
|
(int (*)(sqlite3_vtab_cursor *, sqlite3_context *, int)) my_column,
|
||||||
|
(int (*)(sqlite3_vtab_cursor *, sqlite3_int64 *)) my_rowid,
|
||||||
|
NULL, // my_update
|
||||||
|
NULL, // my_begin
|
||||||
|
NULL, // my_sync
|
||||||
|
NULL, // my_commit
|
||||||
|
NULL, // my_rollback
|
||||||
|
NULL, // my_findfunction
|
||||||
|
NULL, // my_rename
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
destructor(void *arg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
EXPORT int
|
||||||
|
sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) {
|
||||||
|
SQLITE_EXTENSION_INIT2(api);
|
||||||
|
sqlite3_create_module_v2(db, "github", &module, NULL, destructor);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue