Testing Database Engines via Pivoted Query Synthesis
TTesting Database Engines via Pivoted Query Synthesis
Manuel Rigger
Department of Computer ScienceETH ZurichZurich, Switzerland [email protected] Zhendong Su
Department of Computer ScienceETH ZurichZurich, Switzerland [email protected]
ABSTRACT
Relational databases are used ubiquitously. They are man-aged by database management systems (DBMS), which al-low inserting, modifying, and querying data using a domain-specific language called Structured Query Language (SQL).Popular DBMS have been extensively tested by fuzzers, whichhave been successful in finding crash bugs. However, ap-proaches to finding logic bugs , such as when a DBMS com-putes an incorrect result set, have remained mostly untack-led. Differential testing is an effective technique to test sys-tems that support a common language by comparing theoutputs of these systems. However, this technique is ineffec-tive for DBMS, because each DBMS typically supports itsown SQL dialect. To this end, we devised a novel and gen-eral approach that we have termed
Pivoted Query Synthesis .The core idea of this approach is to automatically generatequeries for which we ensure that they fetch a specific, ran-domly selected row, called the pivot row . If the DBMS failsto fetch the pivot row, the likely cause is a bug in the DBMS.We tested our approach on three widely-used and matureDBMS, namely SQLite, MySQL, and PostgreSQL. In total,we reported 123 bugs in these DBMS, 99 of which have beenfixed or verified, demonstrating that the approach is highlyeffective and general. We expect that the wide applicabilityand simplicity of our approach will enable the improvementof robustness of many DBMS.
1. INTRODUCTION
Database management systems (DBMS) based on the rela-tional model [10] are a central component in many applica-tions, since they allow efficiently storing and retrieving data.They have been extensively tested by random query gener-ators such as SQLsmith [30], which have been effective infinding queries that cause the DBMS process to crash ( i.e. ,by causing a buffer overflow). Also fuzzers such as AFL [1]are routinely applied to DBMS. However, these approachescannot detect logic bugs , which we define as bugs that causea query to return an incorrect result, for example, by erro-neously omitting a row, without crashing the DBMS.Logic bugs in DBMS are difficult to detect automatically.A key challenge for automatic testing is to come up withan effective test oracle , that can detect whether a systembehaves correctly for a given input [16]. In 1998, Slutz pro-posed to use differential testing [23] to detect logic bugs inDBMS, by constructing a test oracle that compares the re-sults of a query on multiple DBMS, which he implemented ina tool RAGS [31]. While RAGS detected many bugs, differ-ential testing comes with the significant limitation that the systems under test need to implement the same semanticsfor a given input. All DBMS support a common and stan-dardized language
Structured Query Language (SQL) to cre-ate, access, and modify data [7]. In practice, however, eachDBMS provides a plethora of extensions to this standardand deviates from it in other parts ( e.g. , in how
NULL valuesare handled [31]). This vastly limits differential testing, andalso the author stated that the small common core and thedifferences between different DBMS were a challenge [31].Furthermore, even when all DBMS fetch the same rows, itcannot be ensured that they work correctly, because theymight be affected by the same underlying bug.In order to efficiently detect logic bugs in DBMS, we pro-pose a general and principled approach that we termed
Piv-oted Query Synthesis , which we implemented in a tool calledSQLancer. The core idea is to solve the oracle problem for asingle, randomly-selected row, called the pivot row , by syn-thesizing a query whose result set must contain the pivotrow. By considering only a single row, our approach issimple to understand and implement. We synthesize thequery by randomly generating expressions for
WHERE and
JOIN clauses, evaluating the expressions based on the pivotrow, and modifying each expression to yield
TRUE . If thequery, when processed by the DBMS, fails to fetch the pivotrow, a bug in the DBMS has been detected. We refer to thisoracle as the containment oracle.Listing 1 illustrates our approach on a test case that triggersa bug that we found using the containment oracle in thewidely-used DBMS SQLite. The
CREATE TABLE statementcreates a new table t0 with a column c0 . Subsequently,an index is created and four rows with the values , , , , and NULL are inserted. We select the pivot row c0=NULL and construct the random
WHERE clause c0 IS NOT 1 . Since
NULL IS NOT 1 evaluates to
TRUE , we can directly pass thequery to the DBMS, expecting the row with value
NULL to becontained in the result. However, due to a logic bug in theDBMS, the partial index was used based on the incorrect as-sumption that c0 IS NOT 1 implied c0 NOT NULL , resultingin the pivot row not being fetched. We reported this bug tothe SQLite developers, who stated that it existed since 2013,classified it as critical and fixed it shortly after we reportedit. Even for this simple query, differential testing would havebeen ineffective in detecting the bug. The
CREATE TABLE statement is specific to SQLite, since, unlike other popularDBMS, such as PostgreSQL and MySQL, SQLite does notrequire the column c0 to be assigned a column type. Fur-thermore, both MySQL and PostgreSQL lack an operator IS NOT that can be applied to integers. All DBMS provide1 a r X i v : . [ c s . D B ] J a n n operator IS NOT TRUE , which, however, has different se-mantics; for SQLite, it would fetch only the value , and notexpose the bug. Listing 1: Illustrative example, based on a critical
SQLite bug that we reported.
CREATE TABLE t0(c0);CREATE INDEX i0 ON t0 (1) WHERE c0 NOT NULL ;INSERT INTO t0(c0) VALUES (0) , (1) , (2) , (3) ,( NULL );SELECT c0 FROM t0 WHERE t0.c0 IS NOT 1; --unexpected : NULL is not contained
To demonstrate the generality of our approach, we imple-mented it for three popular and widely-used DBMS, namelySQLite [33], MySQL [26], and PostgreSQL [29]. In total,we found 99 bugs, namely 65 bugs in SQLite, 25 bugs inMySQL, and 9 in PostgreSQL, demonstrating that the ap-proach is highly effective and general. Out of these bugs,we found 61 bugs with the containment oracle. We found34 bugs by causing DBMS-internal errors, such as databasecorruptions, and for 4 bugs we caused DBMS crashes ( i.e. , SEGFAULT s). One of the crashes that we reported for MySQLwas classified as a security vulnerability (CVE-2019-2879).Detailed information on the bug reports and fixes can befound at . We designedour approach to mainly detect logic bugs that cannot befound by fuzzers, which is confirmed by the evaluation.Since our method is general and applicable to all DBMS,we expect that it will be widely adopted to detect bugs thathave so far been overlooked. In summary, we contribute thefollowing: • A general and highly-effective approach to finding bugsin DBMS termed
Pivoted Query Synthesis . • An implementation of this approach in a tool namedSQLancer, used to test SQLite, MySQL, and Post-greSQL. • An evaluation of SQLancer, which uncovered a totalof 99 bugs.
2. BACKGROUND
This section provides important background information onrelational DBMS, SQL, the DBMS we tested, and their char-acteristics.
Database management systems.
DBMS are based ona data model , which abstractly describes how data is orga-nized. In our work, we primarily aim to test DBMS based onthe relational data model proposed by Codd [10], on whichmost widely-used databases, such as Oracle, Microsoft SQL,PostgreSQL, MySQL, and SQLite are based. A relation R inthis model is a mathematical relation R ⊆ S × S × ... × S n where S , S , ..., S n are referred to as domains. More com-monly, a relation is referred to as a table and a domain isreferred to as a data type . Each tuple in this relation isreferred to as a row. Note that rows in R are unordered.While the original relational model did not allow duplicaterows, most DBMS use bags of tuples, which allow duplicatevalues. The approach we present in Section 3 works for bothsets of tuples and bags of tuples. Structured query language (SQL).
Structured QueryLanguage (SQL) [7], which is based on relational algebra [9],
Table 1: The DBMS we tested are popular, complex,and have been developed for a long time.
Popularity RankDBMS DB-Engines StackOver-flow LOC Released Age(years)SQLite 11 4 0.3M 2000 19MySQL 2 1 3.8M 1995 24PostgreSQL 4 2 1.4M 1996 23 is the most commonly used language in DBMS to create ta-bles, insert rows, and manipulate and retrieve data. ANSIfirst standardized SQL in 1987, and it has since been de-veloped further. In practice, however, DBMS lack function-ality described by the SQL standard and deviate from it,making it difficult to test DBMS using differential testing.SQL statements can be roughly categorized as belongingto the Data Definition Language (DDL), Data Manipula-tion Language (DML), and Data Query Language (DQL).DDL statements allow creating, changing, or removing ele-ments such as tables or indexes in a database. For example,
CREATE TABLE allows creating a new table in database witha given schema that defines the columns, their data types,and constraints.
CREATE INDEX creates an index, which is asupplementary data structure used to improve the speed ofquerying data.
ALTER TABLE can be used to change a table’sschema.
DROP statements allow removing elements such astables or indexes. DML statements allow adding, chang-ing, or removing data. For example,
INSERT inserts datainto tables,
UPDATE allows changing values in existing rows,and
DELETE removes rows from a table. The Data QueryLanguage (DQL) allows fetching rows from a database us-ing the
SELECT statement. Although these statements aresupported by all the DBMS that we investigated, the syn-tax and semantics of these statements depend significantlyon the respective DBMS. In fact, many bugs that we foundwere caused by the implementation of features unique to therespective DBMS.
Important DBMS.
We focused on three popular andwidely-used open-source DBMS: SQLite, MySQL, and Post-greSQL (see Table 1). According to the DB-Engines Rank-ing [2] and the Stack Overflow’s annual Developer Sur-vey [27], these DBMS are among the most popular andwidely-used DBMS. Furthermore, the SQLite website specu-lates that SQLite is likely used more than all other databasescombined; most mobile phones extensively use SQLite, it isused in most popular web browsers, and many embeddedsystems (such as television sets). All DBMS have beenmaintained and developed for about 20 years. SQLite isdeveloped by only three developers, MySQL is mainly de-veloped commercially (by Oracle), and PostgreSQL is devel-oped by volunteers coordinated by five people who form thecore team.
Unique Features of the DBMS.
Each DBMS providesits own distinct set of features and characteristics. SQLiteruns in the application process, and thus is mostly used forlocal data storage for individual applications and devices. Itprovides a limited set of language constructs since it strives e.g. , a CSV-based engine). Distinct PostgreSQL featuresinclude object-oriented tables using table inheritance , andthat it performs only few implicit conversion. Test Oracles.
Having an effective test oracle is crucial forautomatic testing approaches [16]. A test oracle can deter-mine for a given test case whether the test case has passedor failed. Manually written test cases encode the program-mer’s knowledge who thus acts a test oracle. In this work,we are only interested in automatic test oracles, which wouldallow comprehensively testing a DBMS. The most success-ful automatic test oracle for DBMS is based on differentialtesting [31]. Differential testing refers to a technique wherea single input is passed to multiple systems that implementthe same language to detect mismatching outputs, whichwould indicate a bug. In the context of DBMS, the inputcorresponds to a database as well as a query, and the sys-tems to multiple DBMS—when their fetched result sets mis-match, a bug in the DBMS would be detected. However, asargued above, DBMS provide different features, making itdifficult to use differential testing effectively. Furthermore,differential testing is not an precise oracle, as it fails to de-tect bugs that are shared by all the systems.
3. Pivoted Query Synthesis
We propose
Pivoted Query Synthesis as an automatic test-ing technique for detecting logic bugs in DBMS. Our coreinsight is that verifying the correctness of the DBMS onerow at a time is simpler than checking the complete resultset, and enables creating a simple test oracle. Specifically,our idea is to select a random row, to which we refer as thepivot row, from a set of tables (and views) in the database.For this pivot row, we semi-randomly generate a set of ex-pressions for which we ensure that they evaluate to
TRUE based on an Abstract Syntax Tree (AST) interpreter. Byusing these expressions in
WHERE and
JOIN clauses of an oth-erwise randomly-generated query, we can ensure that thepivot row must be contained in the result set. If it is notcontained, a bug has been found. By repeatedly checking asingle row, we speculate that this technique is similarly effec-tive as one that verifies the correctness of the complete resultset. Basing the approach on an AST interpreter provides uswith an exact oracle. While implementing this interpreterrequires moderate implementation effort for complex opera-tors (such as regular expression operators), other challengesthat a DBMS has to tackle, such as query planning, con-current access, integrity, and persistence can be disregardedby it. Furthermore, the AST interpreter can be naively im-plemented without affecting the tool’s performance, since itonly operates on a single record, whereas the DBMS has to potentially scan through all the rows of a database toprocess a query.
Figure 1 illustrates the detailed steps of our approach. First,we create a database with one or multiple random tables,which we fill with random data (see step 1 ). We ensurethat each table holds at least one row. We then select arandom row from each of the tables (see step 2 ), to whichwe refer as the pivot row. We verify the correctness of theDBMS based on this pivot row, and also provide a test or-acle. We randomly create expressions based on the DBMS’SQL grammar and valid table column names (see step 3 ).We evaluate these expressions, substituting column refer-ences by the corresponding values of the pivot row. Then,we modify the expressions so that they yield
TRUE (see step4 ). We use these expressions in
WHERE and/or
JOIN clausesfor a query that we construct (see step 5 ). We pass thisquery to the DBMS, which returns a result set (see step6 ), which we expect to contain the pivot row, potentiallyamong other rows. In a final step, we check whether thepivot row is indeed contained in the result set (see step 7 ).If it is not contained, we have likely detected a bug in theDBMS. For the next iteration, we either continue with step2 and generate new queries for a newly-selected pivot row,or continue with 1 to generate a new database.Our core idea is given by how we construct the test ora-cle, which is given by steps 2 to 7 . Thus, Section 3.2first explains how we generate queries and check for contain-ment, assuming that the database has already been created.Section 3.3 then explains step 1 , namely how we generatethe tables and data. While working on the database gen-eration, we found another applicable test oracle to detectbugs, namely by checking for unexpected errors returned bythe DBMS. We refer to this oracle as error oracle and alsoexplain it in Section 3.3. Section 3.4 provides importantimplementation details.
The core idea of our approach is to construct a query forwhich we anticipate that the pivot row is contained in theresult set. We randomly generate expressions to be used ina condition of the query, and ensure that each expressionevaluates to
TRUE for the pivot row. This subsection de-scribes how we generate random expressions that we rectifyand then use in a query ( i.e. , steps 3 to 5 ).
Random Condition Generation.
In step 3 , we ran-domly generate Abstract Syntax Trees (ASTs) up to a spec-ified maximum depth by constructing a random expressiontree based on the database’s schema ( i.e. , the column namesand types). Generating these expression trees is imple-mented similarly to RAGS [31] and SQLsmith [30]. How-ever, while these systems directly use the generated expres-sions in query conditions ( e.g. , in a
WHERE clause), our ap-proach requires the conditions to yield
TRUE for the pivotrow, which is ensured in the subsequent steps. For SQLiteand MySQL, SQLancer generates expressions of any type,because they provide implicit conversions to boolean. ForPostgreSQL, which performs few implicit conversions, thegenerated root node must produce a boolean value, which weachieve by selecting one of the appropriate operators ( e.g. ,a comparison operator).3isting 1 illustrates how generating the expressions is imple-mented for MySQL and SQLite. The input parameter depth ensures that when a specified maximum depth is reached,a leaf node is generated. The leaf node can either be arandomly-generated constant, or a reference to a column ina table or view. If the maximum depth is not yet reached,also other operators are considered (e.g., a unary operatorsuch as
NOT ). Note that generating these expressions is de-pendent on which operators the respective DBMS supports.
Function generateExpression( int depth ) : node types ← { LIT ERAL , COLUMN } if depth < maxdepth then node types ← node types ∪ { UNARY , . . . } type ← random ( node types ) switch type docase LITERAL doreturn
Literal(randomLiteral()); case
COLUMN doreturn
Column-Value(randomTable().randomColumn()); case
UNARY doreturn
UnaryNode(generateExpression(depth+1),UnaryNode.getRandomOperation()); case . . . do . . . endAlgorithm 1: The generateExpression() function gen-erates a random AST.
Expression Evaluation.
After building a random expres-sion tree, we must check whether the condition yields
TRUE for the pivot row. To this end, every node provides an execute() method that computes the node’s result. Leafnodes directly return their assigned constant value. Col-umn nodes are assigned the value that corresponds to theircolumn in the pivot row. For example, in Figure 1 step 3 ,the leaf node t0.c1 returns
TRUE , and the constant node returns an integer . Composite nodes compute their re-sult based on the literals returned by their children. Forexample, the NOT node returns
FALSE , because its child eval-uates to
TRUE (see Algorithm 2). The node first executesits subexpression, and then casts the result to a boolean; ifthe result is a boolean value, the value is negated; other-wise
NULL is returned. Note that our implementation is sim-pler than AST interpreters for programming languages [34],since all nodes operate on literal values ( i.e. , they do notneed to consider mutable storage). Since the bottleneck ofour approach is the DBMS evaluating the queries ratherthan SQLancer, all operations are implemented naively anddo not perform any optimizations. Some operations requiremoderate implementation effort nevertheless; for example,the implementation of the
LIKE regular expression operatorhas over 50 LOC in SQLancer.
Expression Rectification.
After generating random ex-pressions, step 4 ensures that they evaluate to
TRUE . SQLis based on a three-valued logic. Thus, when evaluated in aboolean context, an expression either yields
TRUE , FALSE , or
NULL . To rectify the expression to yield
TRUE , we use Algo-rithm 3. For example, in Figure 1 step 4 , we modify theexpression by adding a preceding
NOT , so that the expres-sion evaluates to
TRUE . Note that our approach works also
Method
NotNode::execute() : value ← child.execute () switch asBoolean(value) docase TRUE do result ← F ALSE case
FALSE do result ← T RUE case
NULL do result ← NULL endreturn result;
Algorithm 2:
The execute() implementation of a
NOT node.for other logic systems ( e.g. , four-valued logic), by adjustingthis step.
Function rectifyCondition( randexpr ) :switch randexpr.execute() docase TRUE do result ← randexpr case FALSE do result ← NOT randexpr case
NULL do result ← randexpr ISNULL endreturn result; Algorithm 3:
The expression rectification step applied toa randomly-generated expression.
Query generation.
In step 5 , we generate targetedqueries that fetch the pivot row. The expressions evaluat-ing to
TRUE are used in
WHERE clauses, which restrict whichrows a query fetches, and in
JOIN clauses, which are usedto join tables. Note that
SELECT statements typically pro-vide various keywords to control the query’s behavior, forexample, all DBMS provide a keyword to fetch only distinctvalues. We randomly select appropriate keywords when gen-erating these queries. Note that our approach can also beused to partially test aggregate functions, which computevalues over multiple rows, when only a single row is presentin a table.
Checking containment.
After using the DBMS toevaluate the query in step 6 , checking whether the pivotrow is part of the result set is the last step of our approach.While the checking routine could have been implementedin SQLancer, we instead construct the query so that itchecks for containment, effectively combining steps 6 and7 . Each DBMS provides various operators to check forcontainment, such as the IN and INTERSECT operators. Forexample, for checking containment in Figure 1 step 7 , wecan check whether the row (3, TRUE, -5) is containedin the result set using the query
SELECT (3, TRUE, -5)INTERSECT SELECT t0.c0, t0.c1, t1.c0 FROM t1, t2WHERE NOT(NOT(t0.c1 OR (t1.c0 > 3))) in SQLite, whichreturns a row if the pivot row is contained.
In step 1 , we generate a random database state. We usethe
CREATE TABLE statement to create tables, and
INSERT t0t1 3 TRUE -5c0 c0 t0.c1t0.c0 t1.c0 TRUE -5 ORNOTt0.c1 >t1.c0 3
SELECT t0.c0, t0.c1, t1.c0
FROM t1, t2
WHERE
NOT (NOT t0.c1 OR (t1.c0 > 3))
DBMS
Randomly select a row from each table
Rectify the expressions to yield TRUE
Generate random query that uses the expressions in WHERE or JOIN clauses Evaluate the query using the DBMS t0.c1 t0.c0 t1.c0 TRUE -52
TRUE Randomlygenerate tables and rows TRUE -5 Generate random expressions and evaluate them based on the selected rows TRUE
FALSE 3 -5 FALSE
FALSE
ORNOTt0.c1 >t1.c0 3
TRUE -5 TRUE
NOT Verify that the row is contained in the result set t0.c1t0.c0 t1.c0 TRUE -52
FALSE ab t0.c1t0.c0 t1.c0 TRUE -5 TRUE
Continue with or Figure 1: Overview of the Approach implemented in SQLancer. Dotted lines indicate that a result isgenerated. to insert data rows. Furthermore, by generating additionalDDL and DML statements, we can explore a larger spaceof databases, some of which exposed DBMS bugs. Forexample, we implemented
UPDATE , DELETE , ALTER TABLE ,and
CREATE INDEX commands for all databases, as well asDBMS-specific run-time options. A number of commandsthat we implemented were unique to the respective DBMS.Statements unique to MySQL were
REPAIR TABLE and
CHECKTABLE . The statements
DISCARD and
CREATE STATISTICS were unique to PostgreSQL.
Error handling.
We attempt to generate statements thatare correct both syntactically and semantically. However,generating semantically correct statements is sometimes im-practical. For example, an
INSERT might fail when a valuealready present in an
UNIQUE column is inserted again; pre-venting such an error would require scanning every row inthe respective table. Rather than checking for such cases,which would involve additional implementation effort and arun-time performance cost, we defined a list of error mes-sages that we might expect when executing the respectivestatement. Often, we associated an error message to astatement depending on presence or absence of specific key-words; for example, an
INSERT OR IGNORE is expected toignore many error messages that would appear without the
OR IGNORE . If the DBMS returns an expected error, it isignored. However, we found a number of cases where anerror message was unexpected. For example, in SQLite a malformed database disk image error message is always un-expected, since it indicates the corruption of the database.Based on this observation, we propose a secondary error or-acle, which we termed error oracle , and which detects a bugwhen an unexpected error is caused.
This section explains implementation decisions, which weconsider significant for the outcome of our study.
Performance.
We optimized SQLancer to take advantageof the underlying hardware. We parallelized the system byrunning each thread on a distinct database, which also re-sulted in bugs connected to race conditions being found. Tofully utilize each CPU, we decreased the probability of SQL statements being generated that cause low CPU utilization(such as
VACUUM in PostgreSQL). Typically, SQLancer gen-erates 5,0000 to 20,000 statements per second, depending onthe DBMS under test. We implemented the system in Java.However, any other programming language would have beenequally well suited, as the performance bottleneck was theDBMS executing the queries.
Number of rows.
We found most bugs by restrictingthe number of rows inserted to a low value (10–30 rows).A higher number would have caused queries to time outwhen tables are joined without a restrictive join clause.For example, in a query
SELECT * FROM t0, t1, t2 , thelargest result set for 100 rows in each table would alreadybe | t | ∗ | t | ∗ | t | = 1 , , Database state.
For the generation of many SQL state-ments, knowledge of the database schema or other databasestate is required; for example, to insert data, SQLancermust determine the name of a table and its columns. Wequery such state dynamically from the DBMS, rather thantracking or computing it ourselves, which would require ad-ditional implementation effort. For example, to query thename of the tables, both MySQL and PostgreSQL provide aninformation table information schema.tables and SQLitea table sqlite master . Expressions on columns.
While our initial implemen-tation only checked the containment of the pivot row, wesubsequently extended it to also check whether expressionson columns are evaluated correctly. To achieve this, we al-low the randomly-generated query to not only refer to a col-umn, but also to randomly-generated expressions that arepotentially based on column references. Thus, rather thanchecking whether the pivot row is contained in the resultset, we evaluate the expressions based on the pivot row tocheck whether the expression results are contained in theresult set.
4. EVALUATION
5e evaluated whether the proposed approach is effectivein finding bugs in DBMS. We expected it to detect logicbugs, which cannot be found by fuzzers, rather than crashbugs. This section overviews the experimental setup, bugsfound, and characterizes the SQL statements used to triggerthe bugs. We then present a DBMS-specific bug overview,where we present interesting bugs and bug trends. Toput these findings into context, we measured the size ofSQLancer’s components and the coverage it reaches on thetested DBMS.
To test the effectiveness of our approach, we implementedSQLancer and tested SQLite, MySQL, and PostgreSQL ina period of about three months. Typically, we enhancedSQLancer to test a new operator or DBMS feature, let thetool run for several seconds up to a day, and then reportany new bugs found during this process. Where possible,we waited for bug fixes before continuing testing and imple-menting new features.
Baseline.
Note that there is no applicable baseline to whichwe could compare our work. RAGS [31], which was proposedmore than 20 years ago, would be the closest related work,but is not publicly available and might be outdated. Due tothe small common SQL core, we would expect that RAGScould not find most of the bugs that we found. Khaleket al. worked on automating testing DBMS using con-straint solving [19, 3], with which they found a previouslyunknown bug. Also their tool is not available publicly. SQL-smith [30], AFL [1] as well as other random query generatorsand fuzzers [28] only detect crash bugs in DBMS. Thus, theonly potential overlap between these tools and SQLancerwould be the crash bugs that we found, which are not thefocus of this work.
DBMS versions.
For all DBMS, we started testing the lat-est release version, which was SQLite 3.28, MySQL 8.0.16,and PostgreSQL 11.4. For SQLite, we switched to the lat-est trunk version ( i.e. , the latest non-release version of thesource code) after the first bugs were fixed. For MySQL,we also tested version 8.0.17 after it was released. For Post-greSQL, we switched to the latest beta version (PostgreSQLBeta 2) after opening duplicate bug reports. Eventually, wecontinued to test the latest trunk version.
Bug reporting.
In the SQLite bug tracker, bugs canonly be created by SQLite developers, so initially we re-ported bugs on the public mailing list. Later, we wereoffered access to the bug tracker and proceeded to reportbugs there. For MySQL, we reported non-security MySQLbugs on the public bug tracker. For PostgreSQL, we re-ported non-security bugs on the public mailing list, sincePostgreSQL lacks a public bug tracker. We reported crashbugs privately, because we were unsure whether they weresecurity relevant; however, we did not investigate any ofthe bugs in terms of their security impact, as the focusof this work were logic bugs. The test cases that we usedin our bug reports were reduced ones; SQLancer automat-ically deletes SQL statements that are unnecessary to re-produce a bug, and we also manually shortened them werepossible. Note that all bug reports are documented at . Table 2: Total number of reported bugs and theirstatus
ClosedDBMS Fixed Verified Intended DuplicateSQLite 65 0 4 2MySQL 15 10 1 4PostgreSQL 5 4 7 6
Table 2 shows the number of bugs that we reported (123overall). We considered 99 bugs as true bugs, because theyresulted in code fixes (77 reports), documentation fixes (8reports), or were confirmed by the developers (14 reports).We opened 24 bug reports that we classified as false bugs,because behavior exhibited in the bug reports was consid-ered to work as intended (12 reports) or because bugs thatwe reported were considered to be duplicates (12 reports, e.g. , because a bug had already been fixed on the latestnon-release version).
Severity levels.
Only for SQLite, bugs were assigned aseverity level by the DBMS developers. 14 bugs were classi-fied as
Critical , 8 bugs as
Severe , and 14 as
Important . For13 bugs, we reported them on the mailing list and no entryin the bug tracker was created. The other bug reports wereassigned low severity levels such as
Minor . While the sever-ity level was not set consistently, this still provides evidencethat we found many critical bugs. For the other DBMS, welack data on how severe the bugs were.
SQLite bug handling.
For SQLite, the main developersreacted to most of our bug reports shortly after reportingthem, and fixed issues typically within a day, which is whyis why no verified and open bugs are listed. The developers’quick responses was a significant factor for the high numberof bugs that we reported for SQLite, which led us to focusour testing efforts on this DBMS. For SQLite, we also tested
VIEWS , non-default
COLLATE s (which define how strings arecompared), floating-point support, and aggregate functions,which we omitted for the other DBMS.
MySQL bug handling.
For MySQL, bug reports weretypically verified within a day by a tester. This tester alsoevaluated whether the bug could be reproduced on otherMySQL versions than the one we specified. MySQL’s de-velopment is not open for the general public. Although wetried to establish contact with MySQL developers, we couldnot obtain any information that went beyond what is visi-ble on the public bug tracker. Thus, it is likely that some ofthe verified bug reports will subsequently be considered asduplicates or classified to work as intended. Furthermore,although MySQL is available as open-source software, onlythe code for the latest release version is provided, so any bugfixes could be verified only with the subsequent release. Thiswas a significant factor that restricted us in finding bugs inMySQL; due to the increased effort of verifying whether anewly found bug was already reported, we invested limitedeffort into testing MySQL.
PostgreSQL bug handling.
For PostgreSQL, we receivedfeedback to bug reports within a day, and it typically took6 able 3: The oracles and how many bugs they found.
DBMS Contains Error SEGFAULTSQLite 46 17 2MySQL 14 10 1PostgreSQL 1 7 1Sum 61 34 4 multiple days or weeks until a bug was fixed, since possiblefixes and patches were discussed intensively on the mailinglist. As we found less bugs for PostgreSQL overall, this didnot significantly influence our testing efforts. Note that notall confirmed bugs were fixed. For example, for one reportedbug, a developer decided to “put this on the back burner un-til we have some consensus how to proceed on that”; fromthe discussion, we speculate that the changes needed to ad-dress the bug properly were considered too invasive.
Test oracles.
Table 3 shows the test oracles that we usedto detect the true bugs. The containment oracle accountsfor most of the bugs that we found, which is expected, sinceour approach mainly builds on this oracle. Perhaps surpris-ingly, the error oracle also contributed a large number ofbugs. We believe that this observation could be used whenusing fuzzers to test DBMS, for example, by checking forspecific error messages that indicate database corruptions.Our approach also detected a number of crash bugs, oneof which was considered a security vulnerability in MySQL.These bugs are somewhat less interesting, since they couldalso have been found by traditional fuzzers.
Test case length.
Our test cases typically comprised onlya few SQL statements (3.71 LOC on average). Note that wereduced test cases before reporting them. Figure 2 showsthe cumulative distribution of the number of statements ina test case to reproduce a bug. For 13 test cases, a sin-gle line was sufficient. Such test cases were either
SELECT statements that operated on constants, or operations thatset DBMS-specific options. As an example, Listing 2 showsa bug in SQLite where subtracting an integer from a
TEXT value resulted in an incorrect result. As another example,Listing 3 shows a bug in MySQL where setting an optionnondeterministically failed with an error. The maximumnumber of statements required to reproduce a bug was 8. APostgreSQL crash bug that had already been fixed when wereported it required even 27 statements to be reproduced.
Listing 2: Bug in SQLite where subtracting an inte-ger from a string produces an incorrect result
SELECT '' - 2851427734582196970; -- actual :-2851427734582196736 , expected :-2851427734582196970 Listing 3: Bug in MySQL where setting an optionnondeterministically failed with an error
SET GLOBAL key_cache_division_limit = 100; --ERROR 1210 ( HY000 ): Incorrect arguments toSET
LOC of the reduced test case c u m u l a t i v e d i s t r i bu t i on Figure 2: The cumulative distribution of LOCneeded to reproduce a bug.Statement distribution.
Figure 3 shows the distributionof statements. Note that for some bug reports, we had to se-lect the simplest test case among multiple failing ones, whichmight skew these results. The
CREATE TABLE and
INSERTstatements are part of most bug reports for all DBMS,which is expected, since only few bugs can be reproducedwithout manipulating or fetching data from a table. 90.0%of the bug reports included only a single table. The
SELECT statement also ranks highly, since the containment oraclerelies on it. In all DBMS, the
CREATE INDEX statementsrank highly; especially for SQLite, we reported a numberof bugs where creating an index resulted in a malformeddatabase image or in a row not being fetched. We foundthat statements that compute or recompute table state wereerror prone, for example,
REPAIR TABLE and
CHECK TABLE inMySQL, as well as
VACUUM and
REINDEX in SQLite and Post-greSQL. DBMS-specific options, such as
SET in MySQL andPostgreSQL, and
PRAGMA in SQLite also resulted in bugs be-ing found. For PostgreSQL, bug reports contained
ANALYZE ,which gathers statistics to be used by the query planner.
Column Constraints.
Column constraints, which can beused to restrict the values stored in a column, were oftenpart of test cases. The most common constraint was
UNIQUE (appearing in 22.2% of the test cases). Also
PRIMARY KEY columns were frequent (17.2%). Typically, the DBMS en-force
UNIQUE and
PRIMARY KEY by creating indexes; explicitindexes, created by
CREATE INDEX were more common, how-ever (28.3%). Other constraints were uncommon, for exam-ple,
FOREIGN KEY s appeared only in 1.0% of the bug reports.
Features.
In SQLite, many bugs resulted from a combina-tion of unique language features. 17 bug reports includedindexes, 11 included COLLATE sequences, and 5
WITHOUTROWID tables. For example, Listing 4 shows a test case forthe first bug that we found with our approach, where thesefeatures were combined, and where SQLite failed to fetch arow. The bug was classified as
Severe and goes back to when
WITHOUT ROWID tables were introduced in 2013. As anotherexample, Listing 5 shows a test case that detected an 11years old
Important bug. This test case uses a
COLLATE , and
WITHOUT ROWID to expose a bug where
RTRIM was imple-mented incorrectly. As mentioned initially, SQLite allowsstoring values of any type in a column, irrespective of itsdeclared type; we discovered 8 bugs related to this feature.For example, Listing 7 shows a minor bug where an opti-mization for the
LIKE operator was implemented incorrectlywhen applied to
INT values.7 ysql postgres sqlite0.0 0.2 0.4 0.6 0.8 0.00 0.25 0.50 0.75 1.00 0.0 0.2 0.4 0.6 0.8DROP INDEXTRANSACTIONCREATE VIEWVACUUMREINDEXANALYZEOPTIONUPDATEALTER TABLECREATE INDEXSELECTINSERTCREATE TABLECREATE STATSDISCARDVACUUMREINDEXANALYZEOPTIONUPDATEALTER TABLECREATE INDEXSELECTINSERTCREATE TABLEREPAIR/CHECK TABLEDROP/CREATE/USE DBOPTIONUPDATEALTER TABLECREATE INDEXSELECTINSERTCREATE TABLE percentage of test cases that included this statement to reproduce the bug legend containserrorsegfaultnot triggering
Figure 3: The distribution of the SQL statements used in the bug reports to reproduce the bug. A non-whitefilling indicates that a statement of the respective category triggered the bug, which was exposed by the testoracle as indicated by the filling ( i.e. , it was the last statement in the bug report).Listing 4: The first bug that we found with our ap-proach involved a
COLLATE index, and a
WITHOUT ROWID table.
CREATE TABLE t0(c0 TEXT PRIMARY KEY ) WITHOUTROWID ;CREATE INDEX i0 ON t0(c1 COLLATE NOCASE );INSERT INTO t0(c0) VALUES ( ' A ' );INSERT INTO t0(c0) VALUES ( ' a ' );SELECT * FROM t0; -- unexpected : only one rowis fetched Listing 5: This test case demonstrates an 11 yearsold
RTRIM bug.
CREATE TABLE t0(c0 COLLATE RTRIM , c1 BLOBUNIQUE , PRIMARY KEY (c0 , c1)) WITHOUT ROWID;INSERT INTO t0 VALUES (123 , 3) , ( ' (cid:32) ' , 1) ,( ' (cid:32)(cid:32)(cid:32)(cid:32)(cid:32)(cid:32) ' , 2) , ( '' , 4);SELECT * FROM t0 WHERE c1 = 1; -- expected : '' , 1, actual : no row is fetched Incorrect optimizations.
A number of bugs could betraced back to incorrect optimizations. For example, List-ing 6 shows a test case that demonstrated that the skip-scanoptimization, where an index is used even if its columns arenot part of the
WHERE clause, was implemented incorrectlyfor
DISTINCT queries. The bug was classified as
Severe .As another example, we found 4 minor bugs in the imple-mentation of an optimization for
LIKE and no-text affinity.Listing 7 demonstrates an example where an exact stringmatch incorrectly yielded
FALSE . Listing 6: SQLite’s skip-scan optimization was im-plemented incorrectly for
DISTINCT
CREATE TABLE t1 (c1 , c2 , c3 , c4 ,PRIMARY KEY (c4 , c3));INSERT INTO t1(c3) VALUES (0) , (0) , (0) , (0) ,(0) , (0) , (0) , (0) , (0) , (0) , ( NULL ), (1) ,(0) ;UPDATE t1 SET c2 = 0;INSERT INTO t1(c1) VALUES (0) , (0) , ( NULL ),(0) , (0) ;ANALYZE t1;UPDATE t1 SET c3 = 1;SELECT DISTINCT * FROM t1 WHERE t1.c3 = 1; --expected : |0|1| , 0||1| , ||1| , actual : |0|1|
Listing 7: We discovered 4 bugs in a
LIKE optimiza-tion, one demonstrated by this test case.
CREATE TABLE t0(c0 INT UNIQUE COLLATE NOCASE );INSERT INTO t0(c0) VALUES ( ' ./ ' );SELECT * FROM t0 WHERE t0.c0 LIKE ' ./ ' ; --unexpected : fetches no rows Language Deficiencies.
RENAME operation, it is ambigu-ous whether the index refers to a string or column, andthe
SELECT fetches C3 as a value for the column c3, whichis incorrect in either case. As another example, Listing 9shows a test case that causes the database schema to dis-agree with the database content, because the behavior ofthe
LIKE operator could be controlled by a run-time flag.The developers stated that this was “a defect in the designof SQLite, not a defect in the implementation”. Seven op-tions to tackle this were outlined, but eventually the issuewas merely documented and a new compile-time option todisable the
PRAGMA was added.
Listing 8: This bug report caused the SQLite devel-opers to disallow double quotes in indexes.
CREATE TABLE t0(c1 , c2);INSERT INTO t0(c1 , c2) VALUES ( ' a ' , 1);CREATE INDEX i0 ON t0("C3");ALTER TABLE t0 RENAME COLUMN c1 TO c3;SELECT DISTINCT * FROM t0; -- fetches C3 |1rather than a|1 Listing 9: The
PRAGMA case sensitive like can causemismatches between the database schema anddatabase content.
CREATE TABLE test (c0);CREATE INDEX index_0 ON test (c0 LIKE '' );PRAGMA case_sensitive_like = false ;VACUUM ;SELECT * from test ; -- Error : malformeddatabase schema ( index_0 ) - non -deterministic functions prohibited in indexexpressions Bugs found with the error oracle.
We discovered 17bugs using the error oracle, the most severe ones being thosethat corrupted the database, which happened in 4 cases, as8ndicated by malformed database schema errors. For exam-ple, Listing 10 shows a test case where manipulating val-ues in a
REAL PRIMARY KEY column resulted in a corrupteddatabase. The bug was introduced in 2015, and went un-detected until we reported it in 2019; it was assigned a
Se-vere severity level. Another common trigger was
REINDEX ,which detected violated constraints, resulting in errors suchas
UNIQUE constraint failed , with which we found 6 bugs.
Listing 10: We found 4 malformed database errorsusing the error oracle, such as this one.
CREATE TABLE t1 (c0 , c1 REAL PRIMARY KEY );INSERT INTO t1(c0 , c1) VALUES (TRUE ,9223372036854775807) , (TRUE , 0);UPDATE t1 SET c0 = NULL ;UPDATE OR REPLACE t1 SET c1 = 1;SELECT DISTINCT * FROM t1 WHERE (t1.c0 IS NULL ); -- database disk image is malformed
Engine-specific bugs.
MySQL provides various enginesthat can be assigned to tables, a feature that is not providedby the other DBMS we tested. The default engine is the
InnoDB engine; an example for an alternative engine is the
MEMORY engine, which stores data in-memory rather than ondisk. We found 5 bugs that were triggered only using suchalternative engines. Listing 11 shows a test case where arow is not fetched using the
MEMORY engine.
Listing 11: We found 5 bugs using non-default en-gines
CREATE TABLE t0(c0 INT );CREATE TABLE t1(c0 INT ) ENGINE = MEMORY ;INSERT INTO t0(c0) VALUES (0) ;INSERT INTO t1(c0) VALUES ( -1);SELECT * FROM t0 , t1 WHERE ( CAST (t1.c0 ASUNSIGNED )) > ( IFNULL ("u", t0.c0)); --expected : row is fetched , actual : no row isfetched
Unsigned integer bugs.
Unlike the other DBMS, MySQLprovides unsigned integer data types. We found 4 bugs re-lated to this feature. For example, also Listing 11 relies ona cast to
UNSIGNED . Value range bugs.
We found a number of bugs wherequeries were handled incorrectly depending on the magni-tude of an integer or floating-point number. For example,Listing 12 shows a bug where the MySQL-specific <=> in-equality operator, which yields a boolean value even when anargument is
NULL , yielded
FALSE when the column value wascompared with a constant that was greater than what thecolumn’s type can represent. Before the release of MySQLversion 8.0.17, we were informed that this would be fixedfor 8.0.18. As another example, we found that small doublevalues ( e.g. , ) stored in TEXT columns incorrectly evalu-ated to
FALSE when used in a boolean context. One suchbug was fixed for version 8.0.17.
Listing 12: Custom comparison operator results inincorrect result
CREATE TABLE t0(c0 TINYINT );INSERT INTO t0(c0) VALUES ( NULL ); SELECT * FROM t0 WHERE NOT (t0.c0 <=>2035382037) ;
Duplicate bugs.
In one case, which we considered as aduplicate, a bug seems to have been fixed already in a versionnot released to the public (see Listing 13). Applying
NOT to anon-zero integer value should yield 0, and negating 0 shouldyield 1. However, it seems that MySQL optimized away thedouble negation, which would be correct for boolean values,but not for other data types, resulting in the row not beingfetched. We believe that the implicit conversions providedby MySQL (and also SQLite) is one of the reasons that wefound more bugs in these DBMS than in PostgreSQL.
Listing 13: Double negation bug
CREATE TABLE t0(c0 INT );INSERT INTO t0(c0) VALUES (1) ;SELECT * FROM t0 WHERE 123 != ( NOT ( NOT 123) );-- expected : row is fetched , actual : row isnot fetched
Segfault.
We found one
SEGFAULT bug in MySQL, whichwas triggered when executing a sequence of SQL statementsusing multiple threads (see Listing 14). To trigger this bug,the
CHECK TABLE statement had to be used, which is uniqueto MySQL. After reporting this error to Oracle, it received aCVE entry (CVE-2019-2879) and was classified as a mediumsecurity vulnerability (CVSS v3.0 Base Score 4.9).
Listing 14: SEGFAULT bug in MySQL
CREATE TABLE t0(c0 INT );CREATE INDEX i0 ON t0 (( t0.c0 || 1));INSERT INTO t0(c0) VALUES (1) ;CHECK TABLE t0 FOR UPGRADE ;
In PostgreSQL, using our containment oracle, we found only1 bug that was fixed. The bug was related to table inher-itance, a feature that only PostgreSQL provides (see List-ing 15). Table t1 inherits from t0 , and PostgreSQL mergesthe c0 column in both tables. As described in the Post-greSQL documentation, t1 does not respect the PRIMARYkey restriction of t0. This was not considered when imple-menting the GROUP BY clause, which caused PostgreSQL toomit one row in its result set.
Listing 15: Table inheritance bug in PostgreSQL
CREATE TABLE t0(c0 INT PRIMARY KEY , c1 INT );CREATE TABLE t1(c0 INT ) INHERITS (t0);INSERT INTO t0(c0 , c1) VALUES (0 , 0);INSERT INTO t1(c0 , c1) VALUES (0 , 1);SELECT c0 , c1 FROM t0 GROUP BY c0 , c1; --expected : 0|0 and 0|1 , actual : 0|0
We found the other 7 bugs using the error oracle. For ex-ample, Listing 16 shows a test case where a
WHERE condi-tion triggered a bug resulting in an error negative bitmapsetmember not allowed . After we reported the bug, on the sameday, another independent bug report was created based ona finding of SQLsmith, which caused PostgreSQL to crashbased on the same underlying bug. This provides further ev-idence that DBMS are being extensively tested and fuzzed.9ote that we also found and reported two structurally dif-ferent crash bugs that exposed this issue, which we laterclassified as duplicates.
Listing 16: Negative bitmapset member error inPostgreSQL
CREATE TABLE t0(c0 serial , c1 boolean );CREATE STATISTICS s1 ON c0 , c1 FROM t0;INSERT INTO t0(c1) VALUES ( TRUE );ANALYZE ;CREATE INDEX i0 ON t0(c0 , (t0.c1 AND t0.c1));SELECT * FROM ( SELECT t0.c0 FROM t0 WHERE ((( t0.c1) AND (t0.c1)) OR FALSE ) IS TRUE ) asresult WHERE result .c0 IS NULL ; --unexpected : ERROR : negative bitmapsetmember not allowed
Multithreaded bugs.
NULL value. In order toreproduce such bugs, we had to record traces of all execut-ing threads. In some cases, reducing or reproducing a bugwas impractical; for example, we encountered a memory leakthat could be observed only after running PostgreSQL for along time.
Listing 17: Unexpected null value bug in Post-greSQL
CREATE TABLE t0(c0 TEXT );INSERT INTO t0(c0) VALUES ( ' b ' ), ( ' a ' );ANALYZE ;INSERT INTO t0(c0) VALUES ( NULL );UPDATE t0 SET c0 = ' a ' ;CREATE INDEX i0 ON t0(c0);SELECT * FROM t0 WHERE ' baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ' > t0.c0; -- unexpected : ERROR : foundunexpected null value in index "i0" False positives.
VACUUM FULL on distinct databases can cause deadlocks tooccur. We created a test case that runs 32 threads to re-produce the deadlock quickly. Responses on the mailinglist concluded that routine use of
VACUUM FULL should beavoided, even more so running 32 threads at once. List-ing 18 shows another issue that we reported that requiresanother thread holding a snapshot of the value in the table. We found that the
VACUUM fails with an errorcaused by an integer overflow, which was surprising to us,since we did not expect
VACUUM to fail. As explained by aPostgreSQL developer, an optimization caused the index tonot be built for that row, so that the issue only surfacedwhen using the
VACUUM . Although this was admitted to besomewhat surprising, addressing this would have had otherdownsides that the developers wanted to avoid.
Listing 18: VACUUM issue in PostgreSQL Table 4: The size of SQLancer’s components specificand common to the tested databases.
LOC CoverageDBMS SQLancer DBMS Ratio Line BranchSQLite 6,501 49,703 13.1% 43.0% 38.4%MySQL 3,995 707,803 0.6% 24.4% 13.0%PostgreSQL 4,981 329,999 1.5% 23.7% 16.6%
CREATE TABLE t1(c0 int );INSERT INTO t1(c0) VALUES (2147483647) ;UPDATE t1 SET c0 = 0;CREATE INDEX i0 ON t1 ((1 + t1.c0));VACUUM FULL ; -- unexpected : ERROR : integer outof range
Duplicates.
Implementation effort.
It is difficult to quantify the effortthat we invested for implementing support for each DBMS,since, for example, we got more efficient in implementingsupport over time. The LOC of code of the individual test-ing components (see Table 4) reflects our estimates that weinvested the most effort to test SQLite, then PostgreSQL,and then MySQL. The code part shared by the componentsis rather small (918 LOC), which provides some evidencefor the different SQL dialects that they support. We believethat the implementation effort for SQLancer is small whencompared to the size of the tested DBMS. Note that theLOC in this table were derived after compiling the respec-tive DBMS using default configurations, and thus includeonly those lines reachable in the binary. Thus, they are sig-nificantly smaller than the ones we derived statically for theentire repositories in Table 1.
Coverage.
To obtain an estimate on how much code ofthe DBMS we tested, we instrumented each DBMS and ranSQLancer for 24 hours on it (see Table 4). The coverageappears to be low (less than 50% for all DBMS); however,this is expected, because we were only concerned about test-ing data-centric SQL statements. MySQL and PostgreSQLprovide features such as user management, replication, anddatabase maintenance functionalities, which we did not test.Furthermore, all DBMS provide consoles to interact withthe DBMS and programming APIs. Furthermore, SQLancerstill lacks support for many common DBMS features. Forexample, we currently do not test many data types, lan-guage elements such transaction savepoints, many DBMS-specific functions, configuration options that can only be setat server start-up time, and operations that might conflictwith other threads running on a distinct database. The cov-erage for SQLite is the highest, reflecting that we invested10ost effort in testing it, but also that it provides fewer fea-tures in addition to its SQL implementation.
5. DISCUSSION
Number of Bugs and Code Quality.
The number ofbugs that we found in the respective DBMS depended onmany, difficult-to-quantify factors. We found most bugs inSQLite. A significant reason for this is that we focused onthis DBMS, because the developers quickly fixed all bugs.Furthermore, while the SQL dialect supported by SQLiteis compact, we perceived it to be the most flexible one, as,for example, column types are not enforced, leading to bugsthat were not present in PostgreSQL, and to a lesser degreein MySQL. MySQL’s release policy made it difficult to testit efficiently, limiting the number of bugs that we found inthis DBMS. In PostgreSQL, we found the least number ofbugs, and we believe that a significant reason for this is thatthe SQL dialect support is strict, and, for example, onlyperforms few implicit conversions.
Existing test efforts.
All three DBMS are extensivelytested. For example, SQLite, for which we found most bugs,has 662 times as much test code and test scripts than sourcecode [32]. The core is tested by three separate test harnesses.The TCL tests comprise 45K test cases, the TH3 proprietarytest harness contains about 1.7 million test instances andprovides 100% branch test coverage and 100% MC/DC testcoverage [18], and the SQL Logic Test runs about 7.2 millionqueries based on over 1 GB of test data. SQLite uses variousfuzzers such as a random query generator called
SQL Fuzz , aproprietary fuzzer dbsqlfuzz , and it is fuzzed by Google’s OSSFuzz project [13]. Other kinds of tests are also applied, suchas crash testing, to demonstrate that the database will notgo corrupt on system crashes or power failures. Consideringthat SQLite and other DBMS are tested this extensively, webelieve that it is surprising that SQLancer could find anybugs.
Reception.
The DBMS developers appreciated our workand effort. For example, for one DBMS, the developersreached out to actively support us in finding new bugs. Asan anecdote, after a bug that we reported was not fixedwithin 2 weeks, they also contacted us to ask whether wehad stopped testing the DBMS; it turned out that the bugreport was overlooked, but then quickly fixed, indicating theimportance of our work. For another DBMS, we were toldthat it is “not often we get that many true positives from atool. We do run fuzzers, but it’s not common to find thatmany bugs in such a short time.”
Relational databases.
Although relational DBMS are themost common form of DBMS, other models also exist, manyto which our approach could be applied. NoSQL DBMSare based on various non-relational, or partly-relationaldata models [6]. For example, MongoDB [25] is a popu-lar, document-oriented DBMS, and thus stores documents rather than rows, where each document describes the data(rather than a schema) and holds the data. Our techniquecould be applied to such a DBMS by selecting random datain a randomly-selected document and then constructing aquery so that the data should be selected.
Implementation effort.
Since the supported SQL dialectsdiffer vastly between DBMS, we had to implement DBMS-specific components in SQLancer. It could be argued thatthe implementation effort is too high, especially when thefull support of a SQL dialect is to be tested, which could ar-guably be similar to implementing a new DBMS. Indeed, wecould not test complex functions such as SQLite’s printf ,which would have required significant implementation ef-fort. However, we still argue that the implementation effortis reasonably low, and allows testing significant parts of aDBMS. Most significantly, our approach effectively evaluatesonly literal expressions, and does not need to consider mul-tiple rows. This obviates the need of implementing a queryplanner, which typically is the most complex component ofa DBMS [12]. Furthermore, the performance of the evalua-tion engine is insignificant; the performance bottleneck wasthe DBMS evaluating the queries, rather than SQLancer.Thus, we also did not implement any optimizations, whichtypically require much implementation effort in DBMS [14].Finally, we did not need to consider aspects such as concur-rency and multi-user control as well as integrity.
Checking a single row.
By checking one row at a time,rather than all the rows, our approach is simple to imple-ment. To compute and evaluate a
WHERE condition, onlyoperations on constants need to be performed, based onthe pivot row. Nevertheless, our approach is, in principle,mostly as effective as an approach that checks all rows, con-sidering that the same SQL statements can be generated forall rows in a table, albeit requiring multiple steps. The onlyobvious conceptual limitation is that we cannot detect logicbugs where a DBMS erroneously fetches duplicate rows.
6. RELATED WORK
Testing of Software Systems.
This paper fits into thestream of testing approaches for important software sys-tems. Differential testing [23] is a technique that comparesthe results obtained by multiple systems that implement acommon language; if results deviate, one or multiple of thesystems are likely to have a bug. It has been used as a ba-sis for many approaches, for example, to test C/C++ com-pilers [35, 36], symbolic execution engines [17], and PDFreaders [20]. Metamorphic testing [8], where the programis transformed so that the same result as for the originalprogram is expected, has been applied to various systems;for example, equivalence modulo inputs is a metamorphic-testing-based approach that has been used to find over onethousand bugs in widely-used compilers [21]. As anotherexample, metamorphic testing has been successfully appliedto test graphic shader compilers [11]. We present
PivotedQuery Synthesis as a novel approach to testing DBMS,which solves the oracle problem in a novel way, namelyby checking whether a DBMS works correctly for a specificquery and row. We believe that our approach can also be ex-tended to test other software systems that have an internalstate, of which a single instance can be selected.
Differential Testing of DBMS.
Slutz proposed an ap-proach
RAGS for finding bugs in DBMS based on differen-tial testing [31]. In
RAGS , queries are automatically gen-erated and evaluated by multiple DBMS. If the results areinconsistent, a bug has been found. As stated in his paper,11he approach was very effective, but is applicable to onlya small set of common SQL statements. In particular, thedifferences in NULL handling, character handling, and nu-meric type coercions were mentioned as problematic. Ourapproach can detect bugs also in SQL statements uniqueto a DBMS, but requires separate implementations for eachDBMS.
Database Fuzzing.
SQLsmith is a popular tool that ran-domly generates SQL queries to test various DBMS [30].SQLsmith has been highly successful and has found over100 bugs in popular DBMS such as PostgreSQL, SQLiteand MonetDB since 2015. However, it cannot find logic bugsfound by our approach. Similarly, general-purpose fuzzerssuch as AFL [1] are routinely applied to DBMS, and havefound many bugs, but also cannot detect logic bugs.
Queries satisfying constraints.
A number of approachesimproved upon random query generation by generatingqueries that satisfy certain constraints, such as cardinali-ties or coverage characteristics. The problem of generat-ing a query, whose subexpressions must satisfy certain con-straints, has been extensively studied [5, 24]; since this prob-lem is complex, it is typically tackled by an approximatealgorithm [5, 24]. An alternative approach was proposed byBati et al. where queries are selected and mutated based onwhether they increase the coverage of rarely executed codepaths [4], increasing the coverage of the DBMS componentunder test. Rather than improved query generation, Lo etal. proposed an approach where a database is generatedbased on specific requirements on test queries [22]. Whilethese approaches improve the query and database genera-tion, they do not help in automatically finding errors, sincethey do not propose an approach to automatically verify thequeries’ results.
DBMS testing based on constraint solving.
Khaleket al. worked on automating testing DBMS using constraintsolving [19, 3]. Their core idea was to use a SAT-based solverto automatically generate database data, queries, and a testoracle. In their first work, they described how to gener-ate query-specific data to populate a database and enumer-ate the rows that would be fetched to construct a test ora-cle [19]. They could reproduce previously-reported and in-jected bugs, but discovered only one new bug. In a follow-upwork, they also demonstrated how the SAT-based approachcan be used to automatically generate queries [3]. As withour approach, they provide a test oracle, and additionally atargeted data generation approach. While both approachesfound bugs, our approach found many previously undiscov-ered bugs. Furthermore, we believe that the simplicity ofour approach could make it wider applicable.
Performance Testing.
Rather than trying to improve thecorrectness of DBMS, several approaches were proposed tomeasure and improve the DBMS optimizer’s performance.Poess. et. al proposed a template-based approach to gen-erating queries suitable to benchmark DBMS, which theyimplemented in a tool QGEN [28]. Similarly to randomquery generators, QGEN could also be used to test DBMS.Gu. et al presented an approach to quantify an optimizer’saccuracy for a given workload by defining a metric over dif-ferent execution plans for this workload, which were gen- erated by using DBMS-specific tuning options [15]. Theyfound significant accuracy differences for optimizers of mul-tiple commercial database systems.
7. CONCLUSION
We have presented an effective approach for detecting bugsin DBMS, which we implemented in a tool SQLancer, withwhich we found over 99 bugs in three popular and widely-used DBMS. The effectiveness of SQLancer is surprising,considering the simplicity of our approach, and that we onlyimplemented a small subset of features that current DBMSsupport. There are a number of promising directions thatcould help uncovering additional bugs, which we regard asfuture work. SQLancer generates tables with a low numberof rows to prevent timeouts of queries when multiple tablesare joined with non-restrictive conditions. By generatingtargeted queries with conditions based on table cardinali-ties [5, 24], we could test the DBMS for a large number ofrows, better stressing the query planner [12]. Some languageelements are difficult to test with our approach, for exam-ple, aggregate functions that compute results over multiplerows. To efficiently test those, metamorphic testing [8] couldbe applied by defining metamorphic relations based on setoperations. Finally, we could also generate conditions andcheck that the pivot row is not contained in the result set,which might uncover additional bugs.
8. ACKNOWLEDGMENTS
We want to thank all the DBMS developers for respondingto our bug reports as well as analyzing and fixing the bugswe reported. We especially want to thank the SQLite de-velopers, D. Richard Hipp and Dan Kennedy, for taking allbugs we reported seriously and fixing them quickly.
9. REFERENCES [1] American Fuzzy Lop, 2019.[2] DB-Engines Ranking (December 2019), 2019.[3] S. Abdul Khalek and S. Khurshid. Automated sqlquery generation for systematic testing of databaseengines. In
Proceedings of the IEEE/ACMInternational Conference on Automated SoftwareEngineering , ASE ’10, pages 329–332, 2010.[4] H. Bati, L. Giakoumakis, S. Herbert, and A. Surna. Agenetic approach for random testing of databasesystems. In
Proceedings of the 33rd InternationalConference on Very Large Data Bases , VLDB ’07,pages 1243–1251. VLDB Endowment, 2007.[5] N. Bruno, S. Chaudhuri, and D. Thomas. Generatingqueries with cardinality constraints for dbms testing.
IEEE Trans. on Knowl. and Data Eng. ,18(12):1721–1725, Dec. 2006.[6] R. Cattell. Scalable sql and nosql data stores.
SIGMOD Rec. , 39(4):12–27, May 2011.[7] D. D. Chamberlin and R. F. Boyce. Sequel: Astructured english query language. In
Proceedings ofthe 1974 ACM SIGFIDET (Now SIGMOD) Workshopon Data Description, Access and Control , SIGFIDET’74, pages 249–264, 1974.[8] T. Y. Chen, S. C. Cheung, and S. M. Yiu.Metamorphic testing: a new approach for generatingnext test cases. Technical report, Technical Report12KUST-CS98-01, Department of Computer Science,Hong Kong, 1998.[9] E. Codd.
Relational Completeness of Data BaseSublanguages . Research report // San Jos´e ResearchLaboratory: Computer sciences. IBM Corporation,1972.[10] E. F. Codd. A relational model of data for largeshared data banks.
Commun. ACM , 13(6):377–387,June 1970.[11] A. F. Donaldson, H. Evrard, A. Lascu, andP. Thomson. Automated testing of graphics shadercompilers.
Proc. ACM Program. Lang. ,1(OOPSLA):93:1–93:29, Oct. 2017.[12] L. Giakoumakis and C. A. Galindo-Legaria. Testingsql server’s query optimizer: Challenges, techniquesand experiences.
IEEE Data Eng. Bull. , 31(1):36–43,2008.[13] Google. Announcing oss-fuzz: Continuous fuzzing foropen source software, 2016.[14] G. Graefe. Query evaluation techniques for largedatabases.
ACM Comput. Surv. , 25(2):73–169, June1993.[15] Z. Gu, M. A. Soliman, and F. M. Waas. Testing theaccuracy of query optimizers. In
Proceedings of theFifth International Workshop on Testing DatabaseSystems , DBTest ’12, pages 11:1–11:6, 2012.[16] W. E. Howden. Theoretical and empirical studies ofprogram testing. In
Proceedings of the 3rdInternational Conference on Software Engineering ,ICSE ’78, pages 305–311, Piscataway, NJ, USA, 1978.IEEE Press.[17] T. Kapus and C. Cadar. Automatic testing ofsymbolic execution engines via program generationand differential testing. In
Proceedings of the 32NdIEEE/ACM International Conference on AutomatedSoftware Engineering , ASE 2017, pages 590–600,Piscataway, NJ, USA, 2017. IEEE Press.[18] H. Kelly J., V. Dan S., C. John J., and R. Leanna K.A practical tutorial on modified condition/decisioncoverage. Technical report, 2001.[19] S. A. Khalek, B. Elkarablieh, Y. O. Laleye, andS. Khurshid. Query-aware test generation using arelational constraint solver. In
Proceedings of the 200823rd IEEE/ACM International Conference onAutomated Software Engineering , ASE ’08, pages238–247, Washington, DC, USA, 2008. IEEEComputer Society.[20] T. Kuchta, T. Lutellier, E. Wong, L. Tan, andC. Cadar. On the correctness of electronic documents: Studying, finding, and localizing inconsistency bugs inpdf readers and files.
Empirical Softw. Engg. ,23(6):3187–3220, Dec. 2018.[21] V. Le, M. Afshari, and Z. Su. Compiler validation viaequivalence modulo inputs. In
Proceedings of the 35thACM SIGPLAN Conference on ProgrammingLanguage Design and Implementation , PLDI ’14,pages 216–226, 2014.[22] E. Lo, C. Binnig, D. Kossmann, M. Tamer ¨Ozsu, andW.-K. Hon. A framework for testing dbms features.
The VLDB Journal , 19(2):203–230, Apr 2010.[23] W. M. McKeeman. Differential testing for software.
Digital Technical Journal , 10(1):100–107, 1998.[24] C. Mishra, N. Koudas, and C. Zuzarte. Generatingtargeted queries for database testing. In
Proceedings ofthe 2008 ACM SIGMOD International Conference onManagement of Data , SIGMOD ’08, pages 499–510,2008.[25] MongoDB. Mongodb homepage, 2019.[26] MySQL. Mysql homepage, 2019.[27] S. Overflow. Developer survey results 2019, 2019.[28] M. Poess and J. M. Stephens, Jr. Generatingthousand benchmark queries in seconds. In
Proceedings of the Thirtieth International Conferenceon Very Large Data Bases - Volume 30 , VLDB ’04,pages 1045–1053. VLDB Endowment, 2004.[29] PostgreSQL. Postgresql homepage, 2019.[30] A. Seltenreich. Sqlsmith, 2019.[31] D. R. Slutz. Massive stochastic testing of sql. In
VLDB , volume 98, pages 618–622, 1998.[32] SQLite. How sqlite is tested, 2019.[33] SQLite. Sqlite homepage, 2019.[34] T. W¨urthinger, C. Wimmer, A. W¨oß, L. Stadler,G. Duboscq, C. Humer, G. Richards, D. Simon, andM. Wolczko. One vm to rule them all. In
Proceedingsof the 2013 ACM International Symposium on NewIdeas, New Paradigms, and Reflections onProgramming & Software , Onward! 2013, pages187–204, 2013.[35] X. Yang, Y. Chen, E. Eide, and J. Regehr. Findingand understanding bugs in c compilers. In
Proceedingsof the 32Nd ACM SIGPLAN Conference onProgramming Language Design and Implementation ,PLDI ’11, pages 283–294, 2011.[36] Q. Zhang, C. Sun, and Z. Su. Skeletal programenumeration for rigorous compiler testing. In