Skip to content

Commit d153496

Browse files
committed
add option to always to require orderby with limit
1 parent 3077fc5 commit d153496

File tree

3 files changed

+51
-1
lines changed

3 files changed

+51
-1
lines changed

testing/differential-oracle/fuzzer/generate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ impl SqlGenBackend {
6363
);
6464
policy.select_config.nulls_order_weights.first = 0;
6565
policy.select_config.nulls_order_weights.last = 0;
66+
policy.select_config.require_order_by_with_limit = true;
6667
// Disable expression values and conflict clauses for now
6768
policy.insert_config.expression_value_probability = 0.0;
6869
policy.insert_config.or_replace_probability = 0.0;

testing/differential-oracle/sql_gen/src/generate/select.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ fn generate_select_impl_inner<C: Capabilities>(
229229
};
230230

231231
// --- ORDER BY ---
232-
let order_by = if ctx.gen_bool_with_prob(order_by_prob) {
232+
let mut order_by = if ctx.gen_bool_with_prob(order_by_prob) {
233233
if let Some(gb) = &group_by {
234234
generate_grouped_order_by(generator, ctx, &gb.exprs)?
235235
} else {
@@ -255,6 +255,15 @@ fn generate_select_impl_inner<C: Capabilities>(
255255
SelectMode::Scalar => (Some(1), None),
256256
};
257257

258+
// Enforce deterministic LIMIT semantics when configured.
259+
if limit.is_some() && order_by.is_empty() && select_config.require_order_by_with_limit {
260+
order_by = if let Some(gb) = &group_by {
261+
generate_grouped_order_by(generator, ctx, &gb.exprs)?
262+
} else {
263+
generate_order_by(generator, ctx)?
264+
};
265+
}
266+
258267
let from = {
259268
let primary_name = ctx.tables_in_scope()[0].table.name.clone();
260269
let primary_qualifier = ctx.tables_in_scope()[0].qualifier.clone();
@@ -1442,4 +1451,35 @@ mod tests {
14421451
}
14431452
}
14441453
}
1454+
1455+
#[test]
1456+
fn test_require_order_by_with_limit_policy() {
1457+
let policy = Policy::default().with_select_config(crate::policy::SelectConfig {
1458+
limit_probability: 1.0,
1459+
order_by_probability: 0.0,
1460+
require_order_by_with_limit: true,
1461+
..Default::default()
1462+
});
1463+
let schema = SchemaBuilder::new()
1464+
.table(Table::new(
1465+
"users",
1466+
vec![
1467+
ColumnDef::new("id", DataType::Integer).primary_key(),
1468+
ColumnDef::new("name", DataType::Text),
1469+
ColumnDef::new("age", DataType::Integer),
1470+
],
1471+
))
1472+
.build();
1473+
let generator: SqlGen<Full> = SqlGen::new(schema, policy);
1474+
1475+
for seed in 0..50 {
1476+
let mut ctx = Context::new_with_seed(seed);
1477+
let select = generate_select_impl(&generator, &mut ctx, SelectMode::Full).unwrap();
1478+
assert!(select.limit.is_some(), "Expected LIMIT to be present");
1479+
assert!(
1480+
!select.order_by.is_empty(),
1481+
"Expected ORDER BY when LIMIT is present with require_order_by_with_limit=true"
1482+
);
1483+
}
1484+
}
14451485
}

testing/differential-oracle/sql_gen/src/policy.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,10 @@ pub struct SelectConfig {
991991
/// Probability of generating LIMIT clause.
992992
pub limit_probability: f64,
993993

994+
/// When true, any SELECT that has a LIMIT will also include ORDER BY.
995+
/// This avoids undefined row-order behavior from unordered LIMIT queries.
996+
pub require_order_by_with_limit: bool,
997+
994998
/// Probability of generating OFFSET clause (only if LIMIT exists).
995999
pub offset_probability: f64,
9961000

@@ -1068,6 +1072,7 @@ impl Default for SelectConfig {
10681072
where_probability: 0.9,
10691073
order_by_probability: 0.7,
10701074
limit_probability: 0.6,
1075+
require_order_by_with_limit: false,
10711076
offset_probability: 0.4,
10721077
group_by_probability: 0.3,
10731078
having_probability: 0.5,
@@ -1100,6 +1105,7 @@ impl SelectConfig {
11001105
where_probability: 0.0,
11011106
order_by_probability: 0.0,
11021107
limit_probability: 0.0,
1108+
require_order_by_with_limit: false,
11031109
offset_probability: 0.0,
11041110
group_by_probability: 0.0,
11051111
having_probability: 0.0,
@@ -1117,6 +1123,7 @@ impl SelectConfig {
11171123
where_probability: 0.95,
11181124
order_by_probability: 0.8,
11191125
limit_probability: 0.7,
1126+
require_order_by_with_limit: false,
11201127
offset_probability: 0.5,
11211128
group_by_probability: 0.5,
11221129
having_probability: 0.6,
@@ -2040,9 +2047,11 @@ mod tests {
20402047
let config = SelectConfig::simple();
20412048
assert_eq!(config.where_probability, 0.0);
20422049
assert_eq!(config.order_by_probability, 0.0);
2050+
assert!(!config.require_order_by_with_limit);
20432051

20442052
let complex = SelectConfig::complex();
20452053
assert!(complex.where_probability > 0.5);
2054+
assert!(!complex.require_order_by_with_limit);
20462055
}
20472056

20482057
#[test]

0 commit comments

Comments
 (0)