11package sqlancer ;
22
3+ import java .sql .SQLException ;
4+ import java .util .HashMap ;
5+ import java .util .Iterator ;
36import java .util .List ;
7+ import java .util .Map ;
48import java .util .stream .Collectors ;
59
610import sqlancer .StateToReproduce .OracleRunReproductionState ;
11+ import sqlancer .common .DBMSCommon ;
712import sqlancer .common .oracle .CompositeTestOracle ;
813import sqlancer .common .oracle .TestOracle ;
914import sqlancer .common .schema .AbstractSchema ;
@@ -14,6 +19,13 @@ public abstract class ProviderAdapter<G extends GlobalState<O, ? extends Abstrac
1419 private final Class <G > globalClass ;
1520 private final Class <O > optionClass ;
1621
22+ // Variables for QPG
23+ Map <String , String > queryPlanPool = new HashMap <>();
24+ static double [] weightedAverageReward ; // static variable for sharing across all threads
25+ int currentSelectRewards ;
26+ int currentSelectCounts ;
27+ int currentMutationOperator = -1 ;
28+
1729 public ProviderAdapter (Class <G > globalClass , Class <O > optionClass ) {
1830 this .globalClass = globalClass ;
1931 this .optionClass = optionClass ;
@@ -67,7 +79,7 @@ public Reproducer<G> generateAndTestDatabase(G globalState) throws Exception {
6779 return null ;
6880 }
6981
70- protected abstract void checkViewsAreValid (G globalState );
82+ protected abstract void checkViewsAreValid (G globalState ) throws SQLException ;
7183
7284 protected TestOracle <G > getTestOracle (G globalState ) throws Exception {
7385 List <? extends OracleFactory <G >> testOracleFactory = globalState .getDbmsSpecificOptions ()
@@ -77,7 +89,11 @@ protected TestOracle<G> getTestOracle(G globalState) throws Exception {
7789 boolean userRequiresMoreThanZeroRows = globalState .getOptions ().testOnlyWithMoreThanZeroRows ();
7890 boolean checkZeroRows = testOracleRequiresMoreThanZeroRows || userRequiresMoreThanZeroRows ;
7991 if (checkZeroRows && globalState .getSchema ().containsTableWithZeroRows (globalState )) {
80- throw new IgnoreMeException ();
92+ if (globalState .getOptions ().enableQPG ()) {
93+ addRowsToAllTables (globalState );
94+ } else {
95+ throw new IgnoreMeException ();
96+ }
8197 }
8298 if (testOracleFactory .size () == 1 ) {
8399 return testOracleFactory .get (0 ).create (globalState );
@@ -94,4 +110,152 @@ protected TestOracle<G> getTestOracle(G globalState) throws Exception {
94110
95111 public abstract void generateDatabase (G globalState ) throws Exception ;
96112
113+ // QPG: entry function
114+ @ Override
115+ public void generateAndTestDatabaseWithQueryPlanGuidance (G globalState ) throws Exception {
116+ if (weightedAverageReward == null ) {
117+ weightedAverageReward = initializeWeightedAverageReward (); // Same length as the list of mutators
118+ }
119+ try {
120+ generateDatabase (globalState );
121+ checkViewsAreValid (globalState );
122+ globalState .getManager ().incrementCreateDatabase ();
123+
124+ Long executedQueryCount = 0L ;
125+ while (executedQueryCount < globalState .getOptions ().getNrQueries ()) {
126+ int numOfNoNewQueryPlans = 0 ;
127+ TestOracle <G > oracle = getTestOracle (globalState );
128+ while (true ) {
129+ try (OracleRunReproductionState localState = globalState .getState ().createLocalState ()) {
130+ assert localState != null ;
131+ try {
132+ oracle .check ();
133+ String query = oracle .getLastQueryString ();
134+ executedQueryCount += 1 ;
135+ if (addQueryPlan (query , globalState )) {
136+ numOfNoNewQueryPlans = 0 ;
137+ } else {
138+ numOfNoNewQueryPlans ++;
139+ }
140+ globalState .getManager ().incrementSelectQueryCount ();
141+ } catch (IgnoreMeException e ) {
142+
143+ }
144+ assert localState != null ;
145+ localState .executedWithoutError ();
146+ }
147+ // exit loop to mutate tables if no new query plans have been found after a while
148+ if (numOfNoNewQueryPlans > globalState .getOptions ().getQPGMaxMutationInterval ()) {
149+ mutateTables (globalState );
150+ break ;
151+ }
152+ }
153+ }
154+ } finally {
155+ globalState .getConnection ().close ();
156+ }
157+ }
158+
159+ // QPG: mutate tables for a new database state
160+ private synchronized boolean mutateTables (G globalState ) throws Exception {
161+ // Update rewards based on a set of newly generated queries in last iteration
162+ if (currentMutationOperator != -1 ) {
163+ weightedAverageReward [currentMutationOperator ] += ((double ) currentSelectRewards
164+ / (double ) currentSelectCounts ) * globalState .getOptions ().getQPGk ();
165+ }
166+ currentMutationOperator = -1 ;
167+
168+ // Choose mutator based on the rewards
169+ int selectedActionIndex = 0 ;
170+ if (Randomly .getPercentage () < globalState .getOptions ().getQPGProbability ()) {
171+ selectedActionIndex = globalState .getRandomly ().getInteger (0 , weightedAverageReward .length );
172+ } else {
173+ selectedActionIndex = DBMSCommon .getMaxIndexInDoubleArrary (weightedAverageReward );
174+ }
175+ int reward = 0 ;
176+
177+ try {
178+ executeMutator (selectedActionIndex , globalState );
179+ checkViewsAreValid (globalState ); // Remove the invalid views
180+ reward = checkQueryPlan (globalState );
181+ } catch (IgnoreMeException | AssertionError e ) {
182+ } finally {
183+ // Update rewards based on existing queries associated with the query plan pool
184+ updateReward (selectedActionIndex , (double ) reward / (double ) queryPlanPool .size (), globalState );
185+ currentMutationOperator = selectedActionIndex ;
186+ }
187+
188+ // Clear the variables for storing the rewards of the action on a set of newly generated queries
189+ currentSelectRewards = 0 ;
190+ currentSelectCounts = 0 ;
191+ return true ;
192+ }
193+
194+ // QPG: add a query plan to the query plan pool and return true if the query plan is new
195+ private boolean addQueryPlan (String selectStr , G globalState ) throws Exception {
196+ String queryPlan = getQueryPlan (selectStr , globalState );
197+
198+ if (globalState .getOptions ().logQueryPlan ()) {
199+ globalState .getLogger ().writeQueryPlan (queryPlan );
200+ }
201+
202+ currentSelectCounts += 1 ;
203+ if (queryPlanPool .containsKey (queryPlan )) {
204+ return false ;
205+ } else {
206+ queryPlanPool .put (queryPlan , selectStr );
207+ currentSelectRewards += 1 ;
208+ return true ;
209+ }
210+ }
211+
212+ // Obtain the reward of the current action based on the queries associated with the query plan pool
213+ private int checkQueryPlan (G globalState ) throws Exception {
214+ int newQueryPlanFound = 0 ;
215+ HashMap <String , String > modifiedQueryPlan = new HashMap <>();
216+ for (Iterator <Map .Entry <String , String >> it = queryPlanPool .entrySet ().iterator (); it .hasNext ();) {
217+ Map .Entry <String , String > item = it .next ();
218+ String queryPlan = item .getKey ();
219+ String selectStr = item .getValue ();
220+ String newQueryPlan = getQueryPlan (selectStr , globalState );
221+ if (newQueryPlan .isEmpty ()) { // Invalid query
222+ it .remove ();
223+ } else if (!queryPlan .equals (newQueryPlan )) { // A query plan has been changed
224+ it .remove ();
225+ modifiedQueryPlan .put (newQueryPlan , selectStr );
226+ if (!queryPlanPool .containsKey (newQueryPlan )) { // A new query plan is found
227+ newQueryPlanFound ++;
228+ }
229+ }
230+ }
231+ queryPlanPool .putAll (modifiedQueryPlan );
232+ return newQueryPlanFound ;
233+ }
234+
235+ // QPG: update the reward of current action
236+ private void updateReward (int actionIndex , double reward , G globalState ) {
237+ weightedAverageReward [actionIndex ] += (reward - weightedAverageReward [actionIndex ])
238+ * globalState .getOptions ().getQPGk ();
239+ }
240+
241+ // QPG: initialize the weighted average reward of all mutation operators (required implementation in specific DBMS)
242+ protected double [] initializeWeightedAverageReward () {
243+ throw new UnsupportedOperationException ();
244+ }
245+
246+ // QPG: obtain the query plan of a query (required implementation in specific DBMS)
247+ protected String getQueryPlan (String selectStr , G globalState ) throws Exception {
248+ throw new UnsupportedOperationException ();
249+ }
250+
251+ // QPG: execute a mutation operator (required implementation in specific DBMS)
252+ protected void executeMutator (int index , G globalState ) throws Exception {
253+ throw new UnsupportedOperationException ();
254+ }
255+
256+ // QPG: add rows to all tables (required implementation in specific DBMS when enabling PQS oracle for QPG)
257+ protected boolean addRowsToAllTables (G globalState ) throws Exception {
258+ throw new UnsupportedOperationException ();
259+ }
260+
97261}
0 commit comments