Skip to content

Commit 0ced3ae

Browse files
committed
feat: add comparison benchmarks against libpg_query and sqlparser-rs
1 parent 3d93942 commit 0ced3ae

File tree

6 files changed

+309
-1
lines changed

6 files changed

+309
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ src/*_parser/*_parser.report
4646
libsqlparser.a
4747
run_tests
4848
run_bench
49+
run_bench_compare
50+
bench/sqlparser_rs_bench/target/

Makefile.new

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ BENCH_TARGET = $(PROJECT_ROOT)/run_bench
5252
CORPUS_TEST_SRC = $(TEST_DIR)/corpus_test.cpp
5353
CORPUS_TEST_TARGET = $(PROJECT_ROOT)/corpus_test
5454

55-
.PHONY: all lib test bench build-corpus-test clean
55+
.PHONY: all lib test bench bench-compare build-corpus-test clean
5656

5757
build-corpus-test: $(CORPUS_TEST_TARGET)
5858

@@ -97,7 +97,26 @@ $(BENCH_TARGET): $(BENCH_OBJS) $(GBENCH_OBJS) $(LIB_TARGET)
9797
$(CORPUS_TEST_TARGET): $(CORPUS_TEST_SRC) $(LIB_TARGET)
9898
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ $< -L$(PROJECT_ROOT) -lsqlparser
9999

100+
# libpg_query comparison benchmark
101+
LIBPGQUERY_DIR = $(PROJECT_ROOT)/third_party/libpg_query
102+
LIBPGQUERY_LIB = $(LIBPGQUERY_DIR)/libpg_query.a
103+
LIBPGQUERY_CPPFLAGS = -I$(LIBPGQUERY_DIR)
104+
105+
BENCH_COMPARE_SRC = $(BENCH_DIR)/bench_comparison.cpp
106+
BENCH_COMPARE_OBJ = $(BENCH_COMPARE_SRC:.cpp=.o)
107+
BENCH_COMPARE_TARGET = $(PROJECT_ROOT)/run_bench_compare
108+
109+
$(BENCH_DIR)/bench_comparison.o: $(BENCH_COMPARE_SRC)
110+
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(GBENCH_CPPFLAGS) $(LIBPGQUERY_CPPFLAGS) -c $< -o $@
111+
112+
bench-compare: $(BENCH_COMPARE_TARGET)
113+
./$(BENCH_COMPARE_TARGET) --benchmark_format=console
114+
115+
$(BENCH_COMPARE_TARGET): $(BENCH_DIR)/bench_main.o $(BENCH_COMPARE_OBJ) $(GBENCH_OBJS) $(LIB_TARGET) $(LIBPGQUERY_LIB)
116+
$(CXX) $(CXXFLAGS) -o $@ $(BENCH_DIR)/bench_main.o $(BENCH_COMPARE_OBJ) $(GBENCH_OBJS) -L$(PROJECT_ROOT) -lsqlparser $(LIBPGQUERY_LIB) -lpthread
117+
100118
clean:
101119
rm -f $(LIB_OBJS) $(LIB_TARGET) $(TEST_OBJS) $(GTEST_OBJ) $(TEST_TARGET)
102120
rm -f $(BENCH_OBJS) $(GBENCH_OBJS) $(BENCH_TARGET) $(CORPUS_TEST_TARGET)
121+
rm -f $(BENCH_COMPARE_OBJ) $(BENCH_COMPARE_TARGET)
103122
@echo "Cleaned."

bench/bench_comparison.cpp

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#include <benchmark/benchmark.h>
2+
#include "sql_parser/parser.h"
3+
#include "sql_parser/emitter.h"
4+
5+
// libpg_query
6+
extern "C" {
7+
#include "pg_query.h"
8+
}
9+
10+
using namespace sql_parser;
11+
12+
// ========== Query set — same queries for both parsers ==========
13+
14+
struct QueryCase {
15+
const char* name;
16+
const char* sql;
17+
};
18+
19+
static const QueryCase comparison_queries[] = {
20+
{"simple_select", "SELECT col FROM t WHERE id = 1"},
21+
{"select_join", "SELECT u.id, o.total FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status = 'active'"},
22+
{"select_complex", "SELECT u.id, u.name, COUNT(o.id) AS order_count "
23+
"FROM users u LEFT JOIN orders o ON u.id = o.user_id "
24+
"WHERE u.status = 'active' "
25+
"GROUP BY u.id, u.name "
26+
"HAVING COUNT(o.id) > 5 "
27+
"ORDER BY order_count DESC LIMIT 50"},
28+
{"insert_values", "INSERT INTO users (name, email) VALUES ('John', 'john@example.com')"},
29+
{"update_simple", "UPDATE users SET status = 'inactive' WHERE last_login < '2024-01-01'"},
30+
{"delete_simple", "DELETE FROM users WHERE id = 42"},
31+
{"set_simple", "SET @@session.wait_timeout = 600"},
32+
{"set_names", "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"},
33+
{"begin", "BEGIN"},
34+
{"create_table", "CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255))"},
35+
};
36+
37+
// ========== Our parser (PostgreSQL dialect for fair comparison) ==========
38+
39+
static void BM_Ours_Select_Simple(benchmark::State& state) {
40+
Parser<Dialect::PostgreSQL> parser;
41+
const char* sql = comparison_queries[0].sql;
42+
size_t len = strlen(sql);
43+
for (auto _ : state) {
44+
auto r = parser.parse(sql, len);
45+
benchmark::DoNotOptimize(r.ast);
46+
}
47+
}
48+
BENCHMARK(BM_Ours_Select_Simple);
49+
50+
static void BM_PgQuery_Select_Simple(benchmark::State& state) {
51+
const char* sql = comparison_queries[0].sql;
52+
for (auto _ : state) {
53+
PgQueryParseResult result = pg_query_parse(sql);
54+
benchmark::DoNotOptimize(result.parse_tree);
55+
pg_query_free_parse_result(result);
56+
}
57+
}
58+
BENCHMARK(BM_PgQuery_Select_Simple);
59+
60+
static void BM_Ours_Select_Join(benchmark::State& state) {
61+
Parser<Dialect::PostgreSQL> parser;
62+
const char* sql = comparison_queries[1].sql;
63+
size_t len = strlen(sql);
64+
for (auto _ : state) {
65+
auto r = parser.parse(sql, len);
66+
benchmark::DoNotOptimize(r.ast);
67+
}
68+
}
69+
BENCHMARK(BM_Ours_Select_Join);
70+
71+
static void BM_PgQuery_Select_Join(benchmark::State& state) {
72+
const char* sql = comparison_queries[1].sql;
73+
for (auto _ : state) {
74+
PgQueryParseResult result = pg_query_parse(sql);
75+
benchmark::DoNotOptimize(result.parse_tree);
76+
pg_query_free_parse_result(result);
77+
}
78+
}
79+
BENCHMARK(BM_PgQuery_Select_Join);
80+
81+
static void BM_Ours_Select_Complex(benchmark::State& state) {
82+
Parser<Dialect::PostgreSQL> parser;
83+
const char* sql = comparison_queries[2].sql;
84+
size_t len = strlen(sql);
85+
for (auto _ : state) {
86+
auto r = parser.parse(sql, len);
87+
benchmark::DoNotOptimize(r.ast);
88+
}
89+
}
90+
BENCHMARK(BM_Ours_Select_Complex);
91+
92+
static void BM_PgQuery_Select_Complex(benchmark::State& state) {
93+
const char* sql = comparison_queries[2].sql;
94+
for (auto _ : state) {
95+
PgQueryParseResult result = pg_query_parse(sql);
96+
benchmark::DoNotOptimize(result.parse_tree);
97+
pg_query_free_parse_result(result);
98+
}
99+
}
100+
BENCHMARK(BM_PgQuery_Select_Complex);
101+
102+
static void BM_Ours_Insert(benchmark::State& state) {
103+
Parser<Dialect::PostgreSQL> parser;
104+
const char* sql = comparison_queries[3].sql;
105+
size_t len = strlen(sql);
106+
for (auto _ : state) {
107+
auto r = parser.parse(sql, len);
108+
benchmark::DoNotOptimize(r.ast);
109+
}
110+
}
111+
BENCHMARK(BM_Ours_Insert);
112+
113+
static void BM_PgQuery_Insert(benchmark::State& state) {
114+
const char* sql = comparison_queries[3].sql;
115+
for (auto _ : state) {
116+
PgQueryParseResult result = pg_query_parse(sql);
117+
benchmark::DoNotOptimize(result.parse_tree);
118+
pg_query_free_parse_result(result);
119+
}
120+
}
121+
BENCHMARK(BM_PgQuery_Insert);
122+
123+
static void BM_Ours_Update(benchmark::State& state) {
124+
Parser<Dialect::PostgreSQL> parser;
125+
const char* sql = comparison_queries[4].sql;
126+
size_t len = strlen(sql);
127+
for (auto _ : state) {
128+
auto r = parser.parse(sql, len);
129+
benchmark::DoNotOptimize(r.ast);
130+
}
131+
}
132+
BENCHMARK(BM_Ours_Update);
133+
134+
static void BM_PgQuery_Update(benchmark::State& state) {
135+
const char* sql = comparison_queries[4].sql;
136+
for (auto _ : state) {
137+
PgQueryParseResult result = pg_query_parse(sql);
138+
benchmark::DoNotOptimize(result.parse_tree);
139+
pg_query_free_parse_result(result);
140+
}
141+
}
142+
BENCHMARK(BM_PgQuery_Update);
143+
144+
static void BM_Ours_Delete(benchmark::State& state) {
145+
Parser<Dialect::PostgreSQL> parser;
146+
const char* sql = comparison_queries[5].sql;
147+
size_t len = strlen(sql);
148+
for (auto _ : state) {
149+
auto r = parser.parse(sql, len);
150+
benchmark::DoNotOptimize(r.ast);
151+
}
152+
}
153+
BENCHMARK(BM_Ours_Delete);
154+
155+
static void BM_PgQuery_Delete(benchmark::State& state) {
156+
const char* sql = comparison_queries[5].sql;
157+
for (auto _ : state) {
158+
PgQueryParseResult result = pg_query_parse(sql);
159+
benchmark::DoNotOptimize(result.parse_tree);
160+
pg_query_free_parse_result(result);
161+
}
162+
}
163+
BENCHMARK(BM_PgQuery_Delete);
164+
165+
static void BM_Ours_Begin(benchmark::State& state) {
166+
Parser<Dialect::PostgreSQL> parser;
167+
const char* sql = comparison_queries[8].sql;
168+
size_t len = strlen(sql);
169+
for (auto _ : state) {
170+
auto r = parser.parse(sql, len);
171+
benchmark::DoNotOptimize(r.stmt_type);
172+
}
173+
}
174+
BENCHMARK(BM_Ours_Begin);
175+
176+
static void BM_PgQuery_Begin(benchmark::State& state) {
177+
const char* sql = comparison_queries[8].sql;
178+
for (auto _ : state) {
179+
PgQueryParseResult result = pg_query_parse(sql);
180+
benchmark::DoNotOptimize(result.parse_tree);
181+
pg_query_free_parse_result(result);
182+
}
183+
}
184+
BENCHMARK(BM_PgQuery_Begin);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "sqlparser-bench"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
sqlparser = "0.52"
8+
criterion = { version = "0.5.1", default-features = false, features = ["cargo_bench_support"] }
9+
10+
[[bench]]
11+
name = "parse_bench"
12+
harness = false
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
use sqlparser::dialect::{MySqlDialect, PostgreSqlDialect};
3+
use sqlparser::parser::Parser;
4+
5+
fn bench_queries(c: &mut Criterion) {
6+
let mysql = MySqlDialect {};
7+
let pgsql = PostgreSqlDialect {};
8+
9+
let queries = vec![
10+
("simple_select", "SELECT col FROM t WHERE id = 1"),
11+
("select_join", "SELECT u.id, o.total FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status = 'active'"),
12+
("select_complex", "SELECT u.id, u.name, COUNT(o.id) AS order_count \
13+
FROM users u LEFT JOIN orders o ON u.id = o.user_id \
14+
WHERE u.status = 'active' \
15+
GROUP BY u.id, u.name \
16+
HAVING COUNT(o.id) > 5 \
17+
ORDER BY order_count DESC LIMIT 50"),
18+
("insert_values", "INSERT INTO users (name, email) VALUES ('John', 'john@example.com')"),
19+
("update_simple", "UPDATE users SET status = 'inactive' WHERE last_login < '2024-01-01'"),
20+
("delete_simple", "DELETE FROM users WHERE id = 42"),
21+
("set_simple", "SET session.wait_timeout = 600"),
22+
("begin", "BEGIN"),
23+
];
24+
25+
// MySQL dialect benchmarks
26+
for (name, sql) in &queries {
27+
c.bench_function(&format!("sqlparser_rs_mysql_{}", name), |b| {
28+
b.iter(|| {
29+
let result = Parser::parse_sql(&mysql, sql);
30+
criterion::black_box(result)
31+
})
32+
});
33+
}
34+
35+
// PostgreSQL dialect benchmarks
36+
for (name, sql) in &queries {
37+
c.bench_function(&format!("sqlparser_rs_pgsql_{}", name), |b| {
38+
b.iter(|| {
39+
let result = Parser::parse_sql(&pgsql, sql);
40+
criterion::black_box(result)
41+
})
42+
});
43+
}
44+
}
45+
46+
criterion_group!(benches, bench_queries);
47+
criterion_main!(benches);

scripts/run_comparison.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/bash
2+
set -e
3+
4+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
6+
cd "$PROJECT_DIR"
7+
8+
echo "=============================================="
9+
echo " SQL Parser Comparison Benchmark"
10+
echo "=============================================="
11+
echo ""
12+
13+
# Build everything with -O3
14+
echo "=== Building release ==="
15+
sed 's/-g -O2/-O3/' Makefile.new > /tmp/Makefile.release
16+
make -f /tmp/Makefile.release clean >/dev/null 2>&1
17+
make -f /tmp/Makefile.release lib >/dev/null 2>&1
18+
19+
# Build libpg_query if available
20+
if [ -f third_party/libpg_query/Makefile ]; then
21+
echo "=== Building libpg_query ==="
22+
(cd third_party/libpg_query && make -j$(nproc) >/dev/null 2>&1) || echo "libpg_query build failed (optional)"
23+
fi
24+
25+
# Run our parser vs libpg_query
26+
if [ -f third_party/libpg_query/libpg_query.a ]; then
27+
echo ""
28+
echo "=== ParserSQL vs libpg_query (PostgreSQL's parser) ==="
29+
echo ""
30+
make -f /tmp/Makefile.release bench-compare 2>&1 | grep "^BM_" | column -t
31+
else
32+
echo "libpg_query not built, skipping comparison"
33+
fi
34+
35+
# Run sqlparser-rs benchmark if Rust is available
36+
if command -v cargo &>/dev/null && [ -d bench/sqlparser_rs_bench ]; then
37+
echo ""
38+
echo "=== sqlparser-rs (Rust) ==="
39+
echo ""
40+
(cd bench/sqlparser_rs_bench && cargo bench 2>&1 | grep "time:" | head -20)
41+
else
42+
echo ""
43+
echo "Rust/cargo not available, skipping sqlparser-rs benchmark"
44+
fi

0 commit comments

Comments
 (0)