Prepared Statements (#43)
Changed PREPARE syntax to be closer to the standard.
This commit is contained in:
parent
5e6cd2d84f
commit
f85a5e7b52
10
Makefile
10
Makefile
|
@ -19,14 +19,19 @@ LIBFLAGS = -shared
|
|||
TARGET = libsqlparser.so
|
||||
INSTALL = /usr/local
|
||||
|
||||
CTESTFLAGS = -Wall -Isrc/ -Itest/ -L./ -std=c++11 -lstdc++ -O3
|
||||
CTESTFLAGS = -Wall -Isrc/ -Itest/ -L./ -std=c++11 -lstdc++
|
||||
|
||||
# Set compile mode to -g or -O3.
|
||||
MODE_LOG = ""
|
||||
mode ?= release
|
||||
ifeq ($(mode), debug)
|
||||
CFLAGS += -g
|
||||
CTESTFLAGS += -g
|
||||
MODE_LOG = "Building in \033[1;31mdebug\033[0m mode"
|
||||
else
|
||||
CFLAGS += -O3
|
||||
CTESTFLAGS += -O3
|
||||
MODE_LOG = "Building in \033[0;32mrelease\033[0m mode ('make mode=debug' for debug mode)"
|
||||
endif
|
||||
|
||||
GMAKE = make mode=$(mode)
|
||||
|
@ -36,7 +41,6 @@ all: library
|
|||
library: $(TARGET)
|
||||
|
||||
$(TARGET): $(LIBOBJ)
|
||||
echo $(mode)
|
||||
$(CXX) $(LIBFLAGS) -o $(TARGET) $(LIBOBJ)
|
||||
|
||||
$(SRCPARSER)/flex_lexer.o: $(SRCPARSER)/flex_lexer.cpp $(SRCPARSER)/bison_parser.cpp
|
||||
|
@ -108,3 +112,5 @@ format:
|
|||
astyle --options=astyle.options $(ALLTEST)
|
||||
astyle --options=astyle.options $(EXAMPLESRC)
|
||||
|
||||
log_mode:
|
||||
@echo $(MODE_LOG)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
#include "SQLParserResult.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace hsql {
|
||||
|
||||
|
@ -95,4 +96,15 @@ namespace hsql {
|
|||
errorColumn_ = -1;
|
||||
}
|
||||
|
||||
// Does NOT take ownership.
|
||||
void SQLParserResult::addParameter(Expr* parameter) {
|
||||
parameters_.push_back(parameter);
|
||||
std::sort(parameters_.begin(), parameters_.end(),
|
||||
[](const Expr* a, const Expr* b) { return a->ival < b->ival; });
|
||||
}
|
||||
|
||||
const std::vector<Expr*>& SQLParserResult::parameters() {
|
||||
return parameters_;
|
||||
}
|
||||
|
||||
} // namespace hsql
|
||||
|
|
|
@ -63,6 +63,11 @@ namespace hsql {
|
|||
// Deletes all statements and other data within the result.
|
||||
void reset();
|
||||
|
||||
// Does NOT take ownership.
|
||||
void addParameter(Expr* parameter);
|
||||
|
||||
const std::vector<Expr*>& parameters();
|
||||
|
||||
private:
|
||||
// List of statements within the result.
|
||||
std::vector<SQLStatement*> statements_;
|
||||
|
@ -78,6 +83,9 @@ namespace hsql {
|
|||
|
||||
// Column number of the occurrance of the error in the query.
|
||||
int errorColumn_;
|
||||
|
||||
// Does NOT have ownership.
|
||||
std::vector<Expr*> parameters_;
|
||||
};
|
||||
|
||||
} // namespace hsql
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -48,7 +48,7 @@
|
|||
extern int hsql_debug;
|
||||
#endif
|
||||
/* "%code requires" blocks. */
|
||||
#line 36 "bison_parser.y" /* yacc.c:1909 */
|
||||
#line 34 "bison_parser.y" /* yacc.c:1909 */
|
||||
|
||||
// %code requires block
|
||||
|
||||
|
@ -214,7 +214,7 @@ extern int hsql_debug;
|
|||
|
||||
union HSQL_STYPE
|
||||
{
|
||||
#line 95 "bison_parser.y" /* yacc.c:1909 */
|
||||
#line 92 "bison_parser.y" /* yacc.c:1909 */
|
||||
|
||||
double fval;
|
||||
int64_t ival;
|
||||
|
|
|
@ -24,8 +24,6 @@ int yyerror(YYLTYPE* llocp, SQLParserResult* result, yyscan_t scanner, const cha
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
%}
|
||||
/*********************************
|
||||
** Section 2: Bison Parser Declarations
|
||||
|
@ -77,7 +75,6 @@ int yyerror(YYLTYPE* llocp, SQLParserResult* result, yyscan_t scanner, const cha
|
|||
@$.first_line = 0;
|
||||
@$.last_line = 0;
|
||||
@$.total_column = 0;
|
||||
@$.placeholder_id = 0;
|
||||
};
|
||||
|
||||
|
||||
|
@ -184,13 +181,13 @@ int yyerror(YYLTYPE* llocp, SQLParserResult* result, yyscan_t scanner, const cha
|
|||
%type <delete_stmt> delete_statement truncate_statement
|
||||
%type <update_stmt> update_statement
|
||||
%type <drop_stmt> drop_statement
|
||||
%type <sval> table_name opt_alias alias file_path
|
||||
%type <sval> table_name opt_alias alias file_path prepare_target_query
|
||||
%type <bval> opt_not_exists opt_distinct
|
||||
%type <uval> import_file_type opt_join_type column_type
|
||||
%type <table> from_clause table_ref table_ref_atomic table_ref_name
|
||||
%type <table> join_clause table_ref_name_no_alias
|
||||
%type <expr> expr operand scalar_expr unary_expr binary_expr logic_expr exists_expr
|
||||
%type <expr> function_expr between_expr star_expr expr_alias placeholder_expr
|
||||
%type <expr> function_expr between_expr star_expr expr_alias param_expr
|
||||
%type <expr> column_name literal int_literal num_literal string_literal
|
||||
%type <expr> comp_expr opt_where join_condition opt_having case_expr in_expr
|
||||
%type <limit> opt_limit opt_top
|
||||
|
@ -242,6 +239,16 @@ input:
|
|||
// Transfers ownership of the statement.
|
||||
result->addStatement(stmt);
|
||||
}
|
||||
|
||||
unsigned param_id = 0;
|
||||
for (void* param : yyloc.param_list) {
|
||||
if (param != nullptr) {
|
||||
Expr* expr = (Expr*) param;
|
||||
expr->ival = param_id;
|
||||
result->addParameter(expr);
|
||||
++param_id;
|
||||
}
|
||||
}
|
||||
delete $1;
|
||||
}
|
||||
;
|
||||
|
@ -253,11 +260,7 @@ statement_list:
|
|||
;
|
||||
|
||||
statement:
|
||||
prepare_statement {
|
||||
$1->setPlaceholders(yyloc.placeholder_list);
|
||||
yyloc.placeholder_list.clear();
|
||||
$$ = $1;
|
||||
}
|
||||
prepare_statement
|
||||
| preparable_statement
|
||||
;
|
||||
|
||||
|
@ -279,22 +282,15 @@ preparable_statement:
|
|||
* Prepared Statement
|
||||
******************************/
|
||||
prepare_statement:
|
||||
PREPARE IDENTIFIER ':' preparable_statement {
|
||||
PREPARE IDENTIFIER FROM prepare_target_query {
|
||||
$$ = new PrepareStatement();
|
||||
$$->name = $2;
|
||||
$$->query = new SQLParserResult($4);
|
||||
}
|
||||
| PREPARE IDENTIFIER '{' statement_list opt_semicolon '}' {
|
||||
$$ = new PrepareStatement();
|
||||
$$->name = $2;
|
||||
$$->query = new SQLParserResult();
|
||||
for (SQLStatement* stmt : *$4) {
|
||||
$$->query->addStatement(stmt);
|
||||
}
|
||||
delete $4;
|
||||
$$->query = $4;
|
||||
}
|
||||
;
|
||||
|
||||
prepare_target_query: STRING
|
||||
|
||||
execute_statement:
|
||||
EXECUTE IDENTIFIER {
|
||||
$$ = new ExecuteStatement();
|
||||
|
@ -717,7 +713,7 @@ column_name:
|
|||
literal:
|
||||
string_literal
|
||||
| num_literal
|
||||
| placeholder_expr
|
||||
| param_expr
|
||||
;
|
||||
|
||||
string_literal:
|
||||
|
@ -738,10 +734,11 @@ star_expr:
|
|||
'*' { $$ = new Expr(kExprStar); }
|
||||
;
|
||||
|
||||
placeholder_expr:
|
||||
param_expr:
|
||||
'?' {
|
||||
$$ = Expr::makePlaceholder(yylloc.total_column);
|
||||
yyloc.placeholder_list.push_back($$);
|
||||
$$ = Expr::makeParameter(yylloc.total_column);
|
||||
$$->ival2 = yyloc.param_list.size();
|
||||
yyloc.param_list.push_back($$);
|
||||
}
|
||||
;
|
||||
|
||||
|
|
|
@ -22,15 +22,12 @@ struct HSQL_CUST_LTYPE {
|
|||
|
||||
int total_column;
|
||||
|
||||
// Placeholder
|
||||
int placeholder_id;
|
||||
std::vector<void*> placeholder_list;
|
||||
// Parameters.
|
||||
// int param_id;
|
||||
std::vector<void*> param_list;
|
||||
};
|
||||
|
||||
#define HSQL_LTYPE HSQL_CUST_LTYPE
|
||||
#define HSQL_LTYPE_IS_DECLARED 1
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
|
@ -118,8 +118,8 @@ namespace hsql {
|
|||
return e;
|
||||
}
|
||||
|
||||
Expr* Expr::makePlaceholder(int id) {
|
||||
Expr* e = new Expr(kExprPlaceholder);
|
||||
Expr* Expr::makeParameter(int id) {
|
||||
Expr* e = new Expr(kExprParameter);
|
||||
e->ival = id;
|
||||
return e;
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ namespace hsql {
|
|||
}
|
||||
|
||||
bool Expr::isLiteral() const {
|
||||
return isType(kExprLiteralInt) || isType(kExprLiteralFloat) || isType(kExprLiteralString) || isType(kExprPlaceholder);
|
||||
return isType(kExprLiteralInt) || isType(kExprLiteralFloat) || isType(kExprLiteralString) || isType(kExprParameter);
|
||||
}
|
||||
|
||||
bool Expr::hasAlias() const {
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace hsql {
|
|||
kExprLiteralString,
|
||||
kExprLiteralInt,
|
||||
kExprStar,
|
||||
kExprPlaceholder,
|
||||
kExprParameter,
|
||||
kExprColumnRef,
|
||||
kExprFunctionRef,
|
||||
kExprOperator,
|
||||
|
@ -124,7 +124,7 @@ namespace hsql {
|
|||
|
||||
static Expr* makeFunctionRef(char* func_name, std::vector<Expr*>* exprList, bool distinct);
|
||||
|
||||
static Expr* makePlaceholder(int id);
|
||||
static Expr* makeParameter(int id);
|
||||
|
||||
static Expr* makeSelect(SelectStatement* select);
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
#include "PrepareStatement.h"
|
||||
|
||||
namespace hsql {
|
||||
// PrepareStatement
|
||||
PrepareStatement::PrepareStatement() :
|
||||
SQLStatement(kStmtPrepare),
|
||||
name(NULL),
|
||||
query(NULL) {}
|
||||
|
||||
PrepareStatement::~PrepareStatement() {
|
||||
free(name);
|
||||
free(query);
|
||||
}
|
||||
} // namespace hsql
|
|
@ -1,32 +1,20 @@
|
|||
#ifndef __SQLPARSER__PREPARE_STATEMENT_H__
|
||||
#define __SQLPARSER__PREPARE_STATEMENT_H__
|
||||
|
||||
#include "../SQLParserResult.h"
|
||||
#include "SQLStatement.h"
|
||||
#include "SelectStatement.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace hsql {
|
||||
|
||||
// Represents SQL Prepare statements.
|
||||
// Example: "PREPARE ins_prep: SELECT * FROM t1 WHERE c1 = ? AND c2 = ?"
|
||||
// Example: PREPARE test FROM 'SELECT * FROM test WHERE a = ?;'
|
||||
struct PrepareStatement : SQLStatement {
|
||||
PrepareStatement();
|
||||
virtual ~PrepareStatement();
|
||||
|
||||
// When setting the placeholders we need to make sure that they are in the correct order.
|
||||
// To ensure that, during parsing we store the character position use that to sort the list here.
|
||||
void setPlaceholders(std::vector<void*> ph);
|
||||
|
||||
char* name;
|
||||
|
||||
// The result that is stored within this prepared statement.
|
||||
SQLParserResult* query;
|
||||
|
||||
// The expressions are not owned by this statement.
|
||||
// Rather they are owned by the query and destroyed, when
|
||||
// the query is destroyed.
|
||||
std::vector<Expr*> placeholders;
|
||||
// The query that is supposed to be prepared.
|
||||
char* query;
|
||||
};
|
||||
|
||||
} // namsepace hsql
|
||||
|
|
|
@ -139,29 +139,6 @@ namespace hsql {
|
|||
}
|
||||
}
|
||||
|
||||
// PrepareStatement
|
||||
PrepareStatement::PrepareStatement() :
|
||||
SQLStatement(kStmtPrepare),
|
||||
name(NULL),
|
||||
query(NULL) {}
|
||||
|
||||
PrepareStatement::~PrepareStatement() {
|
||||
delete query;
|
||||
free(name);
|
||||
}
|
||||
|
||||
void PrepareStatement::setPlaceholders(std::vector<void*> ph) {
|
||||
for (void* e : ph) {
|
||||
if (e != NULL)
|
||||
placeholders.push_back((Expr*) e);
|
||||
}
|
||||
// Sort by col-id
|
||||
std::sort(placeholders.begin(), placeholders.end(), [](Expr * i, Expr * j) -> bool { return (i->ival < j->ival); });
|
||||
|
||||
// Set the placeholder id on the Expr. This replaces the previously stored column id
|
||||
for (uintmax_t i = 0; i < placeholders.size(); ++i) placeholders[i]->ival = i;
|
||||
}
|
||||
|
||||
// SelectStatement.h
|
||||
|
||||
// OrderDescription
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
#include "sql_asserts.h"
|
||||
#include "SQLParser.h"
|
||||
|
||||
using hsql::kExprPlaceholder;
|
||||
using hsql::kExprParameter;
|
||||
using hsql::kExprLiteralInt;
|
||||
|
||||
using hsql::kStmtDrop;
|
||||
using hsql::kStmtExecute;
|
||||
|
@ -21,63 +22,76 @@ using hsql::SelectStatement;
|
|||
|
||||
|
||||
TEST(PrepareSingleStatementTest) {
|
||||
const std::string query = "PREPARE test: SELECT * FROM students WHERE grade = ?;";
|
||||
TEST_PARSE_SINGLE_SQL(query, kStmtPrepare, PrepareStatement, result, prepare);
|
||||
TEST_PARSE_SINGLE_SQL(
|
||||
"PREPARE test FROM 'SELECT * FROM students WHERE grade = ?';",
|
||||
kStmtPrepare,
|
||||
PrepareStatement,
|
||||
result,
|
||||
prepare);
|
||||
|
||||
const SelectStatement* select = (const SelectStatement*) prepare->query->getStatement(0);
|
||||
ASSERT_STREQ(prepare->name, "test");
|
||||
ASSERT_STREQ(prepare->query, "SELECT * FROM students WHERE grade = ?");
|
||||
|
||||
TEST_PARSE_SINGLE_SQL(
|
||||
prepare->query,
|
||||
kStmtSelect,
|
||||
SelectStatement,
|
||||
result2,
|
||||
select);
|
||||
|
||||
ASSERT_EQ(result2.parameters().size(), 1);
|
||||
ASSERT(select->whereClause->expr2->isType(kExprParameter))
|
||||
ASSERT_EQ(select->whereClause->expr2->ival, 0)
|
||||
|
||||
ASSERT(select->whereClause->isSimpleOp('='));
|
||||
ASSERT_EQ(select->whereClause->expr2, prepare->placeholders[0])
|
||||
}
|
||||
|
||||
TEST(PrepareMultiStatementTest) {
|
||||
const std::string query = "PREPARE test {"
|
||||
"INSERT INTO test VALUES(?);"
|
||||
"SELECT ?, test FROM test WHERE c1 = ?;"
|
||||
"};"
|
||||
"PREPARE stmt: SELECT * FROM data WHERE c1 = ?;"
|
||||
"DEALLOCATE PREPARE stmt;";
|
||||
TEST(DeallocatePrepareStatementTest) {
|
||||
TEST_PARSE_SINGLE_SQL(
|
||||
"DEALLOCATE PREPARE test;",
|
||||
kStmtDrop,
|
||||
DropStatement,
|
||||
result,
|
||||
drop);
|
||||
|
||||
TEST_PARSE_SQL_QUERY(query, result, 3);
|
||||
|
||||
TEST_CAST_STMT(result, 0, kStmtPrepare, PrepareStatement, prep1);
|
||||
TEST_CAST_STMT(result, 1, kStmtPrepare, PrepareStatement, prep2);
|
||||
TEST_CAST_STMT(result, 2, kStmtDrop, DropStatement, drop);
|
||||
|
||||
// Prepare Statement #1
|
||||
ASSERT_STREQ(prep1->name, "test");
|
||||
ASSERT_EQ(prep1->placeholders.size(), 3);
|
||||
ASSERT_EQ(prep1->query->size(), 2);
|
||||
|
||||
TEST_CAST_STMT((*prep1->query), 0, kStmtInsert, InsertStatement, insert);
|
||||
TEST_CAST_STMT((*prep1->query), 1, kStmtSelect, SelectStatement, select);
|
||||
|
||||
ASSERT(insert->values->at(0)->isType(kExprPlaceholder));
|
||||
ASSERT(select->selectList->at(0)->isType(kExprPlaceholder));
|
||||
ASSERT(select->whereClause->expr2->isType(kExprPlaceholder));
|
||||
|
||||
// Check IDs of placeholders
|
||||
ASSERT_EQ(insert->values->at(0)->ival, 0);
|
||||
ASSERT_EQ(insert->values->at(0), prep1->placeholders[0]);
|
||||
|
||||
ASSERT_EQ(select->selectList->at(0)->ival, 1);
|
||||
ASSERT_EQ(select->selectList->at(0), prep1->placeholders[1]);
|
||||
|
||||
ASSERT_EQ(select->whereClause->expr2->ival, 2);
|
||||
ASSERT_EQ(select->whereClause->expr2, prep1->placeholders[2]);
|
||||
|
||||
// Prepare Statement #2
|
||||
ASSERT_STREQ(prep2->name, "stmt");
|
||||
ASSERT_EQ(prep2->placeholders.size(), 1);
|
||||
|
||||
// Deallocate Statement
|
||||
ASSERT_EQ(drop->type, kDropPreparedStatement);
|
||||
ASSERT_STREQ(drop->name, "stmt");
|
||||
ASSERT_STREQ(drop->name, "test");
|
||||
}
|
||||
|
||||
|
||||
TEST(StatementWithParameters) {
|
||||
TEST_PARSE_SINGLE_SQL(
|
||||
"SELECT * FROM test WHERE a = ? AND b = ?",
|
||||
kStmtSelect,
|
||||
SelectStatement,
|
||||
result,
|
||||
stmt);
|
||||
|
||||
const hsql::Expr* eq1 = stmt->whereClause->expr;
|
||||
const hsql::Expr* eq2 = stmt->whereClause->expr2;
|
||||
|
||||
ASSERT_EQ(result.parameters().size(), 2);
|
||||
|
||||
ASSERT(eq1->isSimpleOp('='))
|
||||
ASSERT(eq1->expr->isType(hsql::kExprColumnRef))
|
||||
ASSERT(eq1->expr2->isType(kExprParameter))
|
||||
ASSERT_EQ(eq1->expr2->ival, 0)
|
||||
ASSERT_EQ(result.parameters()[0], eq1->expr2);
|
||||
|
||||
|
||||
ASSERT(eq2->isSimpleOp('='))
|
||||
ASSERT(eq2->expr->isType(hsql::kExprColumnRef))
|
||||
ASSERT(eq2->expr2->isType(kExprParameter))
|
||||
ASSERT_EQ(eq2->expr2->ival, 1)
|
||||
ASSERT_EQ(result.parameters()[1], eq2->expr2);
|
||||
}
|
||||
|
||||
TEST(ExecuteStatementTest) {
|
||||
TEST_PARSE_SINGLE_SQL("EXECUTE test(1, 2);", kStmtExecute, ExecuteStatement, result, stmt);
|
||||
TEST_PARSE_SINGLE_SQL(
|
||||
"EXECUTE test(1, 2);",
|
||||
kStmtExecute,
|
||||
ExecuteStatement,
|
||||
result,
|
||||
stmt);
|
||||
|
||||
ASSERT_STREQ(stmt->name, "test");
|
||||
ASSERT_EQ(stmt->parameters->size(), 2);
|
||||
|
|
|
@ -11,6 +11,7 @@ SELECT * FROM t1 UNION (SELECT * FROM t2 UNION SELECT * FROM t3) ORDER BY col1;
|
|||
SELECT TOP 10 * FROM t1 ORDER BY col1, col2;
|
||||
SELECT a, MAX(b), MAX(c, d), CUSTOM(q, UP(r)) AS f FROM t1;
|
||||
SELECT * FROM t WHERE a BETWEEN 1 and c;
|
||||
SELECT * FROM t WHERE a = ? AND b = ?;
|
||||
SELECT City.name, Product.category, SUM(price) FROM fact INNER JOIN City ON fact.city_id = City.id INNER JOIN Product ON fact.product_id = Product.id GROUP BY City.name, Product.category;
|
||||
# JOIN
|
||||
SELECT t1.a, t1.b, t2.c FROM "table" AS t1 JOIN (SELECT * FROM foo JOIN bar ON foo.id = bar.id) t2 ON t1.a = t2.b WHERE (t1.b OR NOT t1.a) AND t2.c = 12.5
|
||||
|
@ -37,8 +38,8 @@ UPDATE students SET grade = 1.0;
|
|||
# DROP
|
||||
DROP TABLE students;
|
||||
# PREPARE
|
||||
PREPARE prep_inst: INSERT INTO test VALUES (?, ?, ?);
|
||||
PREPARE prep2 { INSERT INTO test VALUES (?, 0, 0); INSERT INTO test VALUES (0, ?, 0); INSERT INTO test VALUES (0, 0, ?); };
|
||||
PREPARE prep_inst FROM 'INSERT INTO test VALUES (?, ?, ?)';
|
||||
PREPARE prep2 FROM 'INSERT INTO test VALUES (?, 0, 0); INSERT INTO test VALUES (0, ?, 0); INSERT INTO test VALUES (0, 0, ?);';
|
||||
EXECUTE prep_inst(1, 2, 3);
|
||||
EXECUTE prep;
|
||||
DEALLOCATE PREPARE prep;
|
||||
|
@ -50,3 +51,4 @@ DEALLOCATE PREPARE prep;
|
|||
!CREATE TABLE "table" FROM TBL FILE 'students.tbl';SELECT 1
|
||||
!CREATE TABLE "table" FROM TBL FILE 'students.tbl';1
|
||||
!INSERT INTO test_table VALUESd (1, 2, 'test');
|
||||
!SELECT * FROM t WHERE a = ? AND b = ?;SELECT 1;
|
Loading…
Reference in New Issue