Arquivos
hhvm/hphp/runtime/ext/pdo_sqlite.cpp
T
Drew Paroski 84b9d9a3a2 Separate resources from objects, part 1
In HHVM (and HPHPc before it) we've been piggybacking resources on the
KindOfObject machinery. At the language level, resource is considered to
be a different type than object, and there are a number of differences
in behavior between objects and resources (ex. resources don't allow for
dynamic properties, resources don't work with the clone operator, the
"(object)" cast behaves differently for resources vs. objects, etc).

Piggybacking resources on the KindOfObject machinery has some downsides.
Code that deals with KindOfObject values often needs to check if the value
is a resource and go down a different code path. This makes things harder
to maintain and harder to keep parity with Zend. Also, these extra branches
hurt performance a little, and they make it harder for the JIT to do a good
job in some cases when its generating machine code that operates on objects.

This diff prepares the code base for a new KindOfResource type by adding a
new "Resource" smart pointer type (currently a typedef for the Object smart
pointer type) and it updates the C++ code and the idl files appropriately.
This diff is essentially a cosmetic change and should not impact run time
behavior. In the next diff (part 2) we'll actually add a new KindOfResource
type, detach ResourceData from the ObjectData inheritence hierarchy, and
provide a real implementation for the Resource smart pointer type (instead
of just aliasing the Object smart pointer type).
2013-07-10 11:16:33 -07:00

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 &quoted, 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 &quoted,
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, Resource(new PDOColumn()));
}
}
PDOColumn *col = columns[colno].toResource().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.toResource());
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();
}
///////////////////////////////////////////////////////////////////////////////
}