616 linhas
17 KiB
C++
616 linhas
17 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
| Copyright (c) 1997-2010 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/ext/pdo_sqlite.h"
|
|
#include "hphp/runtime/ext/ext_stream.h"
|
|
#include <sqlite3.h>
|
|
|
|
namespace HPHP {
|
|
IMPLEMENT_DEFAULT_EXTENSION(pdo_sqlite);
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct PDOSqliteError {
|
|
const char *file;
|
|
int line;
|
|
unsigned int errcode;
|
|
char *errmsg;
|
|
};
|
|
|
|
class PDOSqliteConnection : public PDOConnection {
|
|
public:
|
|
PDOSqliteConnection();
|
|
virtual ~PDOSqliteConnection();
|
|
virtual bool create(CArrRef options);
|
|
|
|
int handleError(const char *file, int line, PDOStatement *stmt = NULL);
|
|
|
|
virtual bool support(SupportedMethod method);
|
|
virtual bool closer();
|
|
virtual bool preparer(CStrRef sql, sp_PDOStatement *stmt, CVarRef options);
|
|
virtual int64_t doer(CStrRef sql);
|
|
virtual bool quoter(CStrRef input, String "ed, PDOParamType paramtype);
|
|
virtual bool begin();
|
|
virtual bool commit();
|
|
virtual bool rollback();
|
|
virtual bool setAttribute(int64_t attr, CVarRef value);
|
|
virtual String lastId(const char *name);
|
|
virtual bool fetchErr(PDOStatement *stmt, Array &info);
|
|
virtual int getAttribute(int64_t attr, Variant &value);
|
|
virtual void persistentShutdown();
|
|
|
|
private:
|
|
sqlite3 *m_db;
|
|
PDOSqliteError m_einfo;
|
|
};
|
|
|
|
class PDOSqliteStatement : public PDOStatement {
|
|
public:
|
|
PDOSqliteStatement(sqlite3 *db, sqlite3_stmt* stmt);
|
|
virtual ~PDOSqliteStatement();
|
|
|
|
virtual bool support(SupportedMethod method);
|
|
virtual bool executer();
|
|
virtual bool fetcher(PDOFetchOrientation ori, long offset);
|
|
virtual bool describer(int colno);
|
|
virtual bool getColumn(int colno, Variant &value);
|
|
virtual bool paramHook(PDOBoundParam *param, PDOParamEvent event_type);
|
|
virtual bool getColumnMeta(int64_t colno, Array &return_value);
|
|
virtual bool cursorCloser();
|
|
|
|
private:
|
|
sqlite3 *m_db;
|
|
sqlite3_stmt *m_stmt;
|
|
unsigned m_pre_fetched:1;
|
|
unsigned m_done:1;
|
|
|
|
int handleError(const char *file, int line);
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static int authorizer(void *autharg, int access_type, const char *arg3,
|
|
const char *arg4, const char *arg5, const char *arg6) {
|
|
switch (access_type) {
|
|
case SQLITE_COPY: {
|
|
String filename = File::TranslatePath(arg4);
|
|
return filename.empty() ? SQLITE_DENY : SQLITE_OK;
|
|
}
|
|
case SQLITE_ATTACH: {
|
|
String filename = File::TranslatePath(arg3);
|
|
return filename.empty() ? SQLITE_DENY : SQLITE_OK;
|
|
}
|
|
default:
|
|
return SQLITE_OK; /* access allowed */
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
PDOSqliteConnection::PDOSqliteConnection() : m_db(NULL) {
|
|
m_einfo.file = NULL;
|
|
m_einfo.line = 0;
|
|
m_einfo.errcode = 0;
|
|
m_einfo.errmsg = NULL;
|
|
|
|
alloc_own_columns = 1;
|
|
max_escaped_char_length = 2;
|
|
}
|
|
|
|
PDOSqliteConnection::~PDOSqliteConnection() {
|
|
if (m_db) {
|
|
sqlite3_close(m_db);
|
|
}
|
|
if (m_einfo.errmsg) {
|
|
free(m_einfo.errmsg);
|
|
}
|
|
}
|
|
|
|
bool PDOSqliteConnection::create(CArrRef options) {
|
|
String filename = data_source.substr(0,1) == ":" ? String(data_source) :
|
|
File::TranslatePath(data_source);
|
|
if (filename.empty()) {
|
|
throw_pdo_exception(0, Array(),
|
|
"safe_mode/open_basedir prohibits opening %s",
|
|
data_source.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (sqlite3_open(filename.data(), &m_db) != SQLITE_OK) {
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
sqlite3_set_authorizer(m_db, authorizer, NULL);
|
|
|
|
long timeout = 60;
|
|
if (options.exists(PDO_ATTR_TIMEOUT)) {
|
|
timeout = options[PDO_ATTR_TIMEOUT].toInt64();
|
|
}
|
|
sqlite3_busy_timeout(m_db, timeout * 1000);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteConnection::support(SupportedMethod method) {
|
|
return method != MethodCheckLiveness;
|
|
}
|
|
|
|
int PDOSqliteConnection::handleError(const char *file, int line,
|
|
PDOStatement *stmt /* = NULL */) {
|
|
PDOErrorType *pdo_err = stmt ? &stmt->error_code : &error_code;
|
|
|
|
m_einfo.errcode = sqlite3_errcode(m_db);
|
|
m_einfo.file = file;
|
|
m_einfo.line = line;
|
|
|
|
if (m_einfo.errcode != SQLITE_OK) {
|
|
if (m_einfo.errmsg) {
|
|
free(m_einfo.errmsg);
|
|
}
|
|
m_einfo.errmsg = strdup((char*)sqlite3_errmsg(m_db));
|
|
} else { /* no error */
|
|
strcpy(*pdo_err, PDO_ERR_NONE);
|
|
return false;
|
|
}
|
|
|
|
switch (m_einfo.errcode) {
|
|
case SQLITE_NOTFOUND: strcpy(*pdo_err, "42S02"); break;
|
|
case SQLITE_INTERRUPT: strcpy(*pdo_err, "01002"); break;
|
|
case SQLITE_NOLFS: strcpy(*pdo_err, "HYC00"); break;
|
|
case SQLITE_TOOBIG: strcpy(*pdo_err, "22001"); break;
|
|
case SQLITE_CONSTRAINT: strcpy(*pdo_err, "23000"); break;
|
|
case SQLITE_ERROR:
|
|
default:
|
|
strcpy(*pdo_err, "HY000");
|
|
break;
|
|
}
|
|
|
|
return m_einfo.errcode;
|
|
}
|
|
|
|
bool PDOSqliteConnection::closer() {
|
|
if (m_db) {
|
|
sqlite3_close(m_db);
|
|
m_db = NULL;
|
|
}
|
|
if (m_einfo.errmsg) {
|
|
free(m_einfo.errmsg);
|
|
m_einfo.errmsg = NULL;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteConnection::preparer(CStrRef sql, sp_PDOStatement *stmt,
|
|
CVarRef options) {
|
|
if (options.toArray().exists(PDO_ATTR_CURSOR) &&
|
|
options[PDO_ATTR_CURSOR].toInt64() != PDO_CURSOR_FWDONLY) {
|
|
m_einfo.errcode = SQLITE_ERROR;
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
const char *tail;
|
|
sqlite3_stmt *rawstmt = NULL;
|
|
if (sqlite3_prepare(m_db, sql.data(), sql.size(), &rawstmt, &tail)
|
|
== SQLITE_OK) {
|
|
|
|
PDOSqliteStatement *s = new PDOSqliteStatement(m_db, rawstmt);
|
|
*stmt = s;
|
|
return true;
|
|
}
|
|
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
int64_t PDOSqliteConnection::doer(CStrRef sql) {
|
|
char *errmsg = NULL;
|
|
if (sqlite3_exec(m_db, sql.data(), NULL, NULL, &errmsg) != SQLITE_OK) {
|
|
handleError(__FILE__, __LINE__);
|
|
if (errmsg) sqlite3_free(errmsg);
|
|
return -1;
|
|
}
|
|
return sqlite3_changes(m_db);
|
|
}
|
|
|
|
bool PDOSqliteConnection::quoter(CStrRef input, String "ed,
|
|
PDOParamType paramtype) {
|
|
int len = 2 * input.size() + 3;
|
|
String s(len, ReserveString);
|
|
char *buf = s.mutableSlice().ptr;
|
|
sqlite3_snprintf(len, buf, "'%q'", input.data());
|
|
quoted = s.setSize(strlen(buf));
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteConnection::begin() {
|
|
char *errmsg = NULL;
|
|
if (sqlite3_exec(m_db, "BEGIN", NULL, NULL, &errmsg) != SQLITE_OK) {
|
|
handleError(__FILE__, __LINE__);
|
|
if (errmsg) sqlite3_free(errmsg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteConnection::commit() {
|
|
char *errmsg = NULL;
|
|
if (sqlite3_exec(m_db, "COMMIT", NULL, NULL, &errmsg) != SQLITE_OK) {
|
|
handleError(__FILE__, __LINE__);
|
|
if (errmsg) sqlite3_free(errmsg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteConnection::rollback() {
|
|
char *errmsg = NULL;
|
|
if (sqlite3_exec(m_db, "ROLLBACK", NULL, NULL, &errmsg) != SQLITE_OK) {
|
|
handleError(__FILE__, __LINE__);
|
|
if (errmsg) sqlite3_free(errmsg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteConnection::setAttribute(int64_t attr, CVarRef value) {
|
|
switch (attr) {
|
|
case PDO_ATTR_TIMEOUT:
|
|
sqlite3_busy_timeout(m_db, value.toInt64() * 1000);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String PDOSqliteConnection::lastId(const char *name) {
|
|
return (int64_t)sqlite3_last_insert_rowid(m_db);
|
|
}
|
|
|
|
bool PDOSqliteConnection::fetchErr(PDOStatement *stmt, Array &info) {
|
|
if (m_einfo.errcode) {
|
|
info.append((int64_t)m_einfo.errcode);
|
|
info.append(String(m_einfo.errmsg, CopyString));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int PDOSqliteConnection::getAttribute(int64_t attr, Variant &value) {
|
|
switch (attr) {
|
|
case PDO_ATTR_CLIENT_VERSION:
|
|
case PDO_ATTR_SERVER_VERSION:
|
|
value = String((char *)sqlite3_libversion(), CopyString);
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PDOSqliteConnection::persistentShutdown() {
|
|
// do nothing
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
PDOSqliteStatement::PDOSqliteStatement(sqlite3 *db, sqlite3_stmt* stmt)
|
|
: m_db(db), m_stmt(stmt), m_pre_fetched(0), m_done(0) {
|
|
supports_placeholders = PDO_PLACEHOLDER_POSITIONAL | PDO_PLACEHOLDER_NAMED;
|
|
}
|
|
|
|
PDOSqliteStatement::~PDOSqliteStatement() {
|
|
if (m_stmt) {
|
|
sqlite3_finalize(m_stmt);
|
|
}
|
|
}
|
|
|
|
bool PDOSqliteStatement::support(SupportedMethod method) {
|
|
switch (method) {
|
|
case MethodSetAttribute:
|
|
case MethodGetAttribute:
|
|
case MethodNextRowset:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int PDOSqliteStatement::handleError(const char *file, int line) {
|
|
PDOSqliteConnection *conn = dynamic_cast<PDOSqliteConnection*>(dbh.get());
|
|
assert(conn);
|
|
return conn->handleError(file, line, this);
|
|
}
|
|
|
|
bool PDOSqliteStatement::executer() {
|
|
if (executed && !m_done) {
|
|
sqlite3_reset(m_stmt);
|
|
}
|
|
|
|
m_done = 0;
|
|
switch (sqlite3_step(m_stmt)) {
|
|
case SQLITE_ROW:
|
|
m_pre_fetched = 1;
|
|
column_count = sqlite3_data_count(m_stmt);
|
|
return true;
|
|
|
|
case SQLITE_DONE:
|
|
column_count = sqlite3_column_count(m_stmt);
|
|
row_count = sqlite3_changes(m_db);
|
|
sqlite3_reset(m_stmt);
|
|
m_done = 1;
|
|
return true;
|
|
|
|
case SQLITE_ERROR:
|
|
sqlite3_reset(m_stmt);
|
|
case SQLITE_MISUSE:
|
|
case SQLITE_BUSY:
|
|
default:
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool PDOSqliteStatement::fetcher(PDOFetchOrientation ori, long offset) {
|
|
if (!m_stmt) {
|
|
return false;
|
|
}
|
|
if (m_pre_fetched) {
|
|
m_pre_fetched = 0;
|
|
return true;
|
|
}
|
|
if (m_done) {
|
|
return false;
|
|
}
|
|
switch (sqlite3_step(m_stmt)) {
|
|
case SQLITE_ROW:
|
|
return true;
|
|
|
|
case SQLITE_DONE:
|
|
m_done = 1;
|
|
sqlite3_reset(m_stmt);
|
|
return false;
|
|
|
|
case SQLITE_ERROR:
|
|
sqlite3_reset(m_stmt);
|
|
default:
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool PDOSqliteStatement::describer(int colno) {
|
|
if (colno < 0 || colno >= column_count) {
|
|
/* error invalid column */
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
if (columns.empty()) {
|
|
for (int i = 0; i < column_count; i++) {
|
|
columns.set(i, Object(new PDOColumn()));
|
|
}
|
|
}
|
|
|
|
PDOColumn *col = columns[colno].toObject().getTyped<PDOColumn>();
|
|
col->name = String(sqlite3_column_name(m_stmt, colno), CopyString);
|
|
col->maxlen = 0xffffffff;
|
|
col->precision = 0;
|
|
|
|
switch (sqlite3_column_type(m_stmt, colno)) {
|
|
case SQLITE_INTEGER:
|
|
case SQLITE_FLOAT:
|
|
case SQLITE3_TEXT:
|
|
case SQLITE_BLOB:
|
|
case SQLITE_NULL:
|
|
default:
|
|
col->param_type = PDO_PARAM_STR;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteStatement::getColumn(int colno, Variant &value) {
|
|
if (!m_stmt) {
|
|
return false;
|
|
}
|
|
if (colno >= sqlite3_data_count(m_stmt)) {
|
|
/* error invalid column */
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
char *ptr; int len;
|
|
switch (sqlite3_column_type(m_stmt, colno)) {
|
|
case SQLITE_NULL:
|
|
ptr = NULL;
|
|
len = 0;
|
|
break;
|
|
case SQLITE_BLOB:
|
|
ptr = (char*)sqlite3_column_blob(m_stmt, colno);
|
|
len = sqlite3_column_bytes(m_stmt, colno);
|
|
break;
|
|
case SQLITE3_TEXT:
|
|
ptr = (char*)sqlite3_column_text(m_stmt, colno);
|
|
len = sqlite3_column_bytes(m_stmt, colno);
|
|
break;
|
|
default:
|
|
ptr = (char*)sqlite3_column_text(m_stmt, colno);
|
|
len = sqlite3_column_bytes(m_stmt, colno);
|
|
break;
|
|
}
|
|
value = String(ptr, len, CopyString);
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteStatement::paramHook(PDOBoundParam *param,
|
|
PDOParamEvent event_type) {
|
|
switch (event_type) {
|
|
case PDO_PARAM_EVT_EXEC_PRE:
|
|
if (executed && !m_done) {
|
|
sqlite3_reset(m_stmt);
|
|
m_done = 1;
|
|
}
|
|
|
|
if (param->is_param) {
|
|
if (param->paramno == -1) {
|
|
param->paramno = sqlite3_bind_parameter_index(m_stmt,
|
|
param->name.c_str()) - 1;
|
|
}
|
|
|
|
switch (PDO_PARAM_TYPE(param->param_type)) {
|
|
case PDO_PARAM_STMT:
|
|
return false;
|
|
|
|
case PDO_PARAM_NULL:
|
|
if (sqlite3_bind_null(m_stmt, param->paramno + 1) == SQLITE_OK) {
|
|
return true;
|
|
}
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
|
|
case PDO_PARAM_INT:
|
|
case PDO_PARAM_BOOL:
|
|
if (param->parameter.isNull()) {
|
|
if (sqlite3_bind_null(m_stmt, param->paramno + 1) == SQLITE_OK) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (SQLITE_OK == sqlite3_bind_int(m_stmt, param->paramno + 1,
|
|
param->parameter.toInt64())) {
|
|
return true;
|
|
}
|
|
}
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
|
|
case PDO_PARAM_LOB:
|
|
if (param->parameter.isResource()) {
|
|
Variant buf = f_stream_get_contents(param->parameter.toObject());
|
|
if (!same(buf, false)) {
|
|
param->parameter = buf;
|
|
} else {
|
|
pdo_raise_impl_error(dbh, this, "HY105",
|
|
"Expected a stream resource");
|
|
return false;
|
|
}
|
|
} else if (param->parameter.isNull()) {
|
|
if (sqlite3_bind_null(m_stmt, param->paramno + 1) == SQLITE_OK) {
|
|
return true;
|
|
}
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
{
|
|
String sparam = param->parameter.toString();
|
|
if (SQLITE_OK == sqlite3_bind_blob(m_stmt, param->paramno + 1,
|
|
sparam.data(), sparam.size(),
|
|
SQLITE_STATIC)) {
|
|
return true;
|
|
}
|
|
}
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
|
|
case PDO_PARAM_STR:
|
|
default:
|
|
if (param->parameter.isNull()) {
|
|
if (sqlite3_bind_null(m_stmt, param->paramno + 1) == SQLITE_OK) {
|
|
return true;
|
|
}
|
|
} else {
|
|
String sparam = param->parameter.toString();
|
|
if (SQLITE_OK == sqlite3_bind_text(m_stmt, param->paramno + 1,
|
|
sparam.data(), sparam.size(),
|
|
SQLITE_STATIC)) {
|
|
return true;
|
|
}
|
|
}
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static const StaticString s_native_type("native_type");
|
|
static const StaticString s_null("null");
|
|
static const StaticString s_double("double");
|
|
static const StaticString s_blob("blob");
|
|
static const StaticString s_string("string");
|
|
static const StaticString s_integer("integer");
|
|
static const StaticString s_sqlite_decl_type("sqlite:decl_type");
|
|
static const StaticString s_table("table");
|
|
static const StaticString s_flags("flags");
|
|
|
|
bool PDOSqliteStatement::getColumnMeta(int64_t colno, Array &ret) {
|
|
if (!m_stmt) {
|
|
return false;
|
|
}
|
|
if (colno >= sqlite3_data_count(m_stmt)) {
|
|
/* error invalid column */
|
|
handleError(__FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
ret = Array::Create();
|
|
Array flags = Array::Create();
|
|
switch (sqlite3_column_type(m_stmt, colno)) {
|
|
case SQLITE_NULL: ret.set(s_native_type, s_null); break;
|
|
case SQLITE_FLOAT: ret.set(s_native_type, s_double); break;
|
|
case SQLITE_BLOB: flags.append(s_blob);
|
|
case SQLITE_TEXT: ret.set(s_native_type, s_string); break;
|
|
case SQLITE_INTEGER: ret.set(s_native_type, s_integer); break;
|
|
}
|
|
|
|
const char *str = sqlite3_column_decltype(m_stmt, colno);
|
|
if (str) {
|
|
ret.set(s_sqlite_decl_type, String((char *)str, CopyString));
|
|
}
|
|
|
|
#ifdef SQLITE_ENABLE_COLUMN_METADATA
|
|
str = sqlite3_column_table_name(m_stmt, colno);
|
|
if (str) {
|
|
ret.set(s_table, String((char *)str, CopyString));
|
|
}
|
|
#endif
|
|
|
|
ret.set(s_flags, flags);
|
|
return true;
|
|
}
|
|
|
|
bool PDOSqliteStatement::cursorCloser() {
|
|
sqlite3_reset(m_stmt);
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
PDOSqlite::PDOSqlite() : PDODriver("sqlite") {
|
|
}
|
|
|
|
PDOConnection *PDOSqlite::createConnectionObject() {
|
|
return new PDOSqliteConnection();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|