Merge 'Add support for unlikely(X)' from bit-aloo

Implements the unlikely(X) function. Removes runtime implementations of
likely(), unlikely() and likelihood(), replacing them with panics if
they reach the VDBE.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #2559
This commit is contained in:
Jussi Saurio
2025-08-14 10:56:27 +03:00
committed by GitHub
4 changed files with 33 additions and 95 deletions

View File

@@ -264,7 +264,7 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
| unhex(X) | Yes | |
| unhex(X,Y) | Yes | |
| unicode(X) | Yes | |
| unlikely(X) | No | |
| unlikely(X) | Yes | |
| upper(X) | Yes | |
| zeroblob(N) | Yes | |

View File

@@ -331,6 +331,7 @@ pub enum ScalarFunc {
BinRecordJsonObject,
Attach,
Detach,
Unlikely,
}
impl ScalarFunc {
@@ -393,6 +394,7 @@ impl ScalarFunc {
ScalarFunc::BinRecordJsonObject => true,
ScalarFunc::Attach => false, // changes database state
ScalarFunc::Detach => false, // changes database state
ScalarFunc::Unlikely => true,
}
}
}
@@ -457,6 +459,7 @@ impl Display for ScalarFunc {
Self::BinRecordJsonObject => "bin_record_json_object".to_string(),
Self::Attach => "attach".to_string(),
Self::Detach => "detach".to_string(),
Self::Unlikely => "unlikely".to_string(),
};
write!(f, "{str}")
}
@@ -748,6 +751,7 @@ impl Func {
"replace" => Ok(Self::Scalar(ScalarFunc::Replace)),
"likely" => Ok(Self::Scalar(ScalarFunc::Likely)),
"likelihood" => Ok(Self::Scalar(ScalarFunc::Likelihood)),
"unlikely" => Ok(Self::Scalar(ScalarFunc::Unlikely)),
#[cfg(feature = "json")]
"json" => Ok(Self::Json(JsonFunc::Json)),
#[cfg(feature = "json")]

View File

@@ -1710,20 +1710,13 @@ pub fn translate_expr(
} else {
crate::bail_parse_error!("likely function with no arguments",);
};
let start_reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
start_reg,
target_register,
resolver,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
ScalarFunc::Likelihood => {
@@ -1760,20 +1753,13 @@ pub fn translate_expr(
"second argument of likelihood() must be a numeric literal",
);
}
let start_reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
start_reg,
target_register,
resolver,
)?;
program.emit_insn(Insn::Copy {
src_reg: start_reg,
dst_reg: target_register,
extra_amount: 0,
});
Ok(target_register)
}
ScalarFunc::TableColumnsJsonArray => {
@@ -1841,6 +1827,27 @@ pub fn translate_expr(
"DETACH should be handled at statement level, not as expression"
);
}
ScalarFunc::Unlikely => {
let args = if let Some(args) = args {
if args.len() != 1 {
crate::bail_parse_error!(
"Unlikely function must have exactly 1 argument",
);
}
args
} else {
crate::bail_parse_error!("Unlikely function with no arguments",);
};
translate_expr(
program,
referenced_tables,
&args[0],
target_register,
resolver,
)?;
Ok(target_register)
}
}
}
Func::Math(math_func) => match math_func.arity() {

View File

@@ -4473,20 +4473,6 @@ pub fn op_function(
let result = exec_printf(&state.registers[*start_reg..*start_reg + arg_count])?;
state.registers[*dest] = Register::Value(result);
}
ScalarFunc::Likely => {
let value = &state.registers[*start_reg].borrow_mut();
let result = value.get_owned_value().exec_likely();
state.registers[*dest] = Register::Value(result);
}
ScalarFunc::Likelihood => {
assert_eq!(arg_count, 2);
let value = &state.registers[*start_reg];
let probability = &state.registers[*start_reg + 1];
let result = value
.get_owned_value()
.exec_likelihood(probability.get_owned_value());
state.registers[*dest] = Register::Value(result);
}
ScalarFunc::TableColumnsJsonArray => {
assert_eq!(arg_count, 1);
#[cfg(not(feature = "json"))]
@@ -4652,6 +4638,11 @@ pub fn op_function(
// Set result to NULL (detach doesn't return a value)
state.registers[*dest] = Register::Value(Value::Null);
}
ScalarFunc::Unlikely | ScalarFunc::Likely | ScalarFunc::Likelihood => {
panic!(
"{scalar_func:?} should be stripped during expression translation and never reach VDBE",
);
}
},
crate::function::Func::Vector(vector_func) => match vector_func {
VectorFunc::Vector => {
@@ -7989,14 +7980,6 @@ impl Value {
Value::Float(result)
}
fn exec_likely(&self) -> Value {
self.clone()
}
fn exec_likelihood(&self, _probability: &Value) -> Value {
self.clone()
}
pub fn exec_add(&self, rhs: &Value) -> Value {
(Numeric::from(self) + Numeric::from(rhs)).into()
}
@@ -10067,62 +10050,6 @@ mod tests {
);
}
#[test]
fn test_likely() {
let input = Value::build_text("limbo");
let expected = Value::build_text("limbo");
assert_eq!(input.exec_likely(), expected);
let input = Value::Integer(100);
let expected = Value::Integer(100);
assert_eq!(input.exec_likely(), expected);
let input = Value::Float(12.34);
let expected = Value::Float(12.34);
assert_eq!(input.exec_likely(), expected);
let input = Value::Null;
let expected = Value::Null;
assert_eq!(input.exec_likely(), expected);
let input = Value::Blob(vec![1, 2, 3, 4]);
let expected = Value::Blob(vec![1, 2, 3, 4]);
assert_eq!(input.exec_likely(), expected);
}
#[test]
fn test_likelihood() {
let value = Value::build_text("limbo");
let prob = Value::Float(0.5);
assert_eq!(value.exec_likelihood(&prob), value);
let value = Value::build_text("database");
let prob = Value::Float(0.9375);
assert_eq!(value.exec_likelihood(&prob), value);
let value = Value::Integer(100);
let prob = Value::Float(1.0);
assert_eq!(value.exec_likelihood(&prob), value);
let value = Value::Float(12.34);
let prob = Value::Float(0.5);
assert_eq!(value.exec_likelihood(&prob), value);
let value = Value::Null;
let prob = Value::Float(0.5);
assert_eq!(value.exec_likelihood(&prob), value);
let value = Value::Blob(vec![1, 2, 3, 4]);
let prob = Value::Float(0.5);
assert_eq!(value.exec_likelihood(&prob), value);
let prob = Value::build_text("0.5");
assert_eq!(value.exec_likelihood(&prob), value);
let prob = Value::Null;
assert_eq!(value.exec_likelihood(&prob), value);
}
#[test]
fn test_bitfield() {
let mut bitfield = Bitfield::<4>::new();