mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-10 10:44:22 +01:00
Merge 'introduce eq/contains/starts_with/ends_with_ignore_ascii_case macros' from Lâm Hoàng Phúc
depend on #2865 ```sh `ALTER TABLE _ RENAME TO _`/limbo_rename_table/ time: [10.100 ms 10.191 ms 10.283 ms] change: [-16.770% -15.559% -14.417%] (p = 0.00 < 0.05) Performance has improved. Found 2 outliers among 100 measurements (2.00%) 1 (1.00%) low mild 1 (1.00%) high mild `ALTER TABLE _ RENAME COLUMN _ TO _`/limbo_rename_column/ time: [7.4829 ms 7.5492 ms 7.6128 ms] change: [-19.397% -18.093% -16.789%] (p = 0.00 < 0.05) Performance has improved. Found 4 outliers among 100 measurements (4.00%) 3 (3.00%) low mild 1 (1.00%) high mild `ALTER TABLE _ ADD COLUMN _`/limbo_add_column/ time: [5.3255 ms 5.3713 ms 5.4183 ms] change: [-24.002% -22.612% -21.195%] (p = 0.00 < 0.05) Performance has improved. Found 39 outliers among 100 measurements (39.00%) 17 (17.00%) low severe 1 (1.00%) low mild 1 (1.00%) high mild 20 (20.00%) high severe `ALTER TABLE _ DROP COLUMN _`/limbo_drop_column/ time: [5.8858 ms 5.9183 ms 5.9510 ms] change: [-16.233% -14.679% -13.083%] (p = 0.00 < 0.05) Performance has improved. Found 25 outliers among 100 measurements (25.00%) 8 (8.00%) low severe 11 (11.00%) low mild 2 (2.00%) high mild 4 (4.00%) high severe Prepare `SELECT 1`/limbo_parse_query/SELECT 1 time: [590.28 ns 591.31 ns 592.35 ns] change: [-3.7810% -3.5059% -3.2444%] (p = 0.00 < 0.05) Performance has improved. Found 7 outliers among 100 measurements (7.00%) 1 (1.00%) low severe 6 (6.00%) high mild Prepare `SELECT * FROM users LIMIT 1`/limbo_parse_query/SELECT * FROM users LIMIT 1 time: [1.2569 µs 1.2582 µs 1.2596 µs] change: [-5.0125% -4.7516% -4.4933%] (p = 0.00 < 0.05) Performance has improved. Found 7 outliers among 100 measurements (7.00%) 3 (3.00%) low severe 2 (2.00%) low mild 1 (1.00%) high mild 1 (1.00%) high severe Prepare `SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY cou... time: [3.7180 µs 3.7227 µs 3.7274 µs] change: [-3.0557% -2.7642% -2.4761%] (p = 0.00 < 0.05) Performance has improved. Found 6 outliers among 100 measurements (6.00%) 2 (2.00%) low mild 4 (4.00%) high mild Execute `SELECT 1`/limbo_execute_select_1 time: [27.455 ns 27.477 ns 27.499 ns] change: [-2.9461% -2.7493% -2.5589%] (p = 0.00 < 0.05) Performance has improved. Found 3 outliers among 100 measurements (3.00%) 1 (1.00%) low mild 1 (1.00%) high mild 1 (1.00%) high severe Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/1 time: [410.53 ns 411.05 ns 411.54 ns] change: [-15.364% -15.133% -14.912%] (p = 0.00 < 0.05) Performance has improved. Found 5 outliers among 100 measurements (5.00%) 4 (4.00%) low mild 1 (1.00%) high mild Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/10 time: [2.1100 µs 2.1122 µs 2.1145 µs] change: [-11.517% -11.065% -10.662%] (p = 0.00 < 0.05) Performance has improved. Found 4 outliers among 100 measurements (4.00%) 2 (2.00%) low severe 2 (2.00%) low mild Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/50 time: [9.5156 µs 9.5268 µs 9.5383 µs] change: [-10.284% -10.086% -9.8833%] (p = 0.00 < 0.05) Performance has improved. Found 3 outliers among 100 measurements (3.00%) 1 (1.00%) low severe 2 (2.00%) low mild Execute `SELECT * FROM users LIMIT ?`/limbo_execute_select_rows/100 time: [18.669 µs 18.698 µs 18.731 µs] change: [-9.5949% -9.3407% -9.1140%] (p = 0.00 < 0.05) Performance has improved. Found 2 outliers among 100 measurements (2.00%) 1 (1.00%) low severe 1 (1.00%) high mild Execute `SELECT count() FROM users`/limbo_execute_select_count time: [7.1027 µs 7.1098 µs 7.1170 µs] change: [-43.739% -43.596% -43.469%] (p = 0.00 < 0.05) Performance has improved. Found 9 outliers among 100 measurements (9.00%) 2 (2.00%) low mild 5 (5.00%) high mild 2 (2.00%) high severe ``` Closes #2866
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
use crate::LimboError::InvalidModifier;
|
||||
use crate::Result;
|
||||
use crate::{ends_with_ignore_ascii_case, eq_ignore_ascii_case, starts_with_ignore_ascii_case};
|
||||
use crate::{types::Value, vdbe::Register};
|
||||
use chrono::{
|
||||
DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta, TimeZone, Timelike, Utc,
|
||||
};
|
||||
use turso_macros::match_ignore_ascii_case;
|
||||
|
||||
/// Execution of date/time/datetime functions
|
||||
#[inline(always)]
|
||||
@@ -544,102 +546,123 @@ fn parse_modifier_time(s: &str) -> Result<NaiveTime> {
|
||||
}
|
||||
|
||||
fn parse_modifier(modifier: &str) -> Result<Modifier> {
|
||||
let modifier = modifier.trim().to_lowercase();
|
||||
let modifier = modifier.trim().as_bytes();
|
||||
|
||||
match modifier.as_str() {
|
||||
#[inline(always)]
|
||||
fn from_bytes(bytes: &[u8]) -> &str {
|
||||
unsafe { str::from_utf8_unchecked(bytes) }
|
||||
} // safe because input is from &str
|
||||
|
||||
match_ignore_ascii_case!(match modifier {
|
||||
// exact matches first
|
||||
"ceiling" => Ok(Modifier::Ceiling),
|
||||
"floor" => Ok(Modifier::Floor),
|
||||
"start of month" => Ok(Modifier::StartOfMonth),
|
||||
"start of year" => Ok(Modifier::StartOfYear),
|
||||
"start of day" => Ok(Modifier::StartOfDay),
|
||||
s if s.starts_with("weekday ") => {
|
||||
let day = parse_modifier_number(&s[8..])?;
|
||||
if !(0..=6).contains(&day) {
|
||||
Err(InvalidModifier(
|
||||
"Weekday must be between 0 and 6".to_string(),
|
||||
))
|
||||
} else {
|
||||
Ok(Modifier::Weekday(day as u32))
|
||||
}
|
||||
}
|
||||
"unixepoch" => Ok(Modifier::UnixEpoch),
|
||||
"julianday" => Ok(Modifier::JulianDay),
|
||||
"auto" => Ok(Modifier::Auto),
|
||||
"localtime" => Ok(Modifier::Localtime),
|
||||
"utc" => Ok(Modifier::Utc),
|
||||
"subsec" | "subsecond" => Ok(Modifier::Subsec),
|
||||
s if s.ends_with(" day") => Ok(Modifier::Days(parse_modifier_number(&s[..s.len() - 4])?)),
|
||||
s if s.ends_with(" days") => Ok(Modifier::Days(parse_modifier_number(&s[..s.len() - 5])?)),
|
||||
s if s.ends_with(" hour") => Ok(Modifier::Hours(parse_modifier_number(&s[..s.len() - 5])?)),
|
||||
s if s.ends_with(" hours") => {
|
||||
Ok(Modifier::Hours(parse_modifier_number(&s[..s.len() - 6])?))
|
||||
}
|
||||
s if s.ends_with(" minute") => {
|
||||
Ok(Modifier::Minutes(parse_modifier_number(&s[..s.len() - 7])?))
|
||||
}
|
||||
s if s.ends_with(" minutes") => {
|
||||
Ok(Modifier::Minutes(parse_modifier_number(&s[..s.len() - 8])?))
|
||||
}
|
||||
s if s.ends_with(" second") => {
|
||||
Ok(Modifier::Seconds(parse_modifier_number(&s[..s.len() - 7])?))
|
||||
}
|
||||
s if s.ends_with(" seconds") => {
|
||||
Ok(Modifier::Seconds(parse_modifier_number(&s[..s.len() - 8])?))
|
||||
}
|
||||
s if s.ends_with(" month") => Ok(Modifier::Months(
|
||||
parse_modifier_number(&s[..s.len() - 6])? as i32,
|
||||
)),
|
||||
s if s.ends_with(" months") => Ok(Modifier::Months(
|
||||
parse_modifier_number(&s[..s.len() - 7])? as i32,
|
||||
)),
|
||||
s if s.ends_with(" year") => Ok(Modifier::Years(
|
||||
parse_modifier_number(&s[..s.len() - 5])? as i32
|
||||
)),
|
||||
s if s.ends_with(" years") => Ok(Modifier::Years(
|
||||
parse_modifier_number(&s[..s.len() - 6])? as i32,
|
||||
)),
|
||||
s if s.starts_with('+') || s.starts_with('-') => {
|
||||
let sign = if s.starts_with('-') { -1 } else { 1 };
|
||||
let parts: Vec<&str> = s[1..].split(' ').collect();
|
||||
let digits_in_date = 10;
|
||||
match parts.len() {
|
||||
1 => {
|
||||
if parts[0].len() == digits_in_date {
|
||||
let date = parse_modifier_date(parts[0])?;
|
||||
Ok(Modifier::DateOffset {
|
||||
years: sign * date.year(),
|
||||
months: sign * date.month() as i32,
|
||||
days: sign * date.day() as i32,
|
||||
})
|
||||
b"ceiling" => Ok(Modifier::Ceiling),
|
||||
b"floor" => Ok(Modifier::Floor),
|
||||
b"start of month" => Ok(Modifier::StartOfMonth),
|
||||
b"start of year" => Ok(Modifier::StartOfYear),
|
||||
b"start of day" => Ok(Modifier::StartOfDay),
|
||||
b"unixepoch" => Ok(Modifier::UnixEpoch),
|
||||
b"julianday" => Ok(Modifier::JulianDay),
|
||||
b"auto" => Ok(Modifier::Auto),
|
||||
b"localtime" => Ok(Modifier::Localtime),
|
||||
b"utc" => Ok(Modifier::Utc),
|
||||
b"subsec" | b"subsecond" => Ok(Modifier::Subsec),
|
||||
_ => {
|
||||
match modifier {
|
||||
s if starts_with_ignore_ascii_case!(s, b"weekday ") => {
|
||||
let day = parse_modifier_number(from_bytes(&s[8..]))?;
|
||||
if !(0..=6).contains(&day) {
|
||||
Err(InvalidModifier(
|
||||
"Weekday must be between 0 and 6".to_string(),
|
||||
))
|
||||
} else {
|
||||
// time values are either 12, 8 or 5 digits
|
||||
let time = parse_modifier_time(parts[0])?;
|
||||
let time_delta = sign * (time.num_seconds_from_midnight() as i32);
|
||||
Ok(Modifier::TimeOffset(TimeDelta::seconds(time_delta.into())))
|
||||
Ok(Modifier::Weekday(day as u32))
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let date = parse_modifier_date(parts[0])?;
|
||||
let time = parse_modifier_time(parts[1])?;
|
||||
// Convert time to total seconds (with sign)
|
||||
let time_delta = sign * (time.num_seconds_from_midnight() as i32);
|
||||
Ok(Modifier::DateTimeOffset {
|
||||
years: sign * (date.year()),
|
||||
months: sign * (date.month() as i32),
|
||||
days: sign * date.day() as i32,
|
||||
seconds: time_delta,
|
||||
})
|
||||
s if ends_with_ignore_ascii_case!(s, b" day") => Ok(Modifier::Days(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 4]))?,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" days") => Ok(Modifier::Days(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 5]))?,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" hour") => Ok(Modifier::Hours(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 5]))?,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" hours") => Ok(Modifier::Hours(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 6]))?,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" minute") => Ok(Modifier::Minutes(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 7]))?,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" minutes") => Ok(Modifier::Minutes(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 8]))?,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" second") => Ok(Modifier::Seconds(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 7]))?,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" seconds") => Ok(Modifier::Seconds(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 8]))?,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" month") => Ok(Modifier::Months(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 6]))? as i32,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" months") => Ok(Modifier::Months(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 7]))? as i32,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" year") => Ok(Modifier::Years(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 5]))? as i32,
|
||||
)),
|
||||
s if ends_with_ignore_ascii_case!(s, b" years") => Ok(Modifier::Years(
|
||||
parse_modifier_number(from_bytes(&s[..s.len() - 6]))? as i32,
|
||||
)),
|
||||
s if starts_with_ignore_ascii_case!(s, b"+")
|
||||
|| starts_with_ignore_ascii_case!(s, b"-") =>
|
||||
{
|
||||
let sign = if starts_with_ignore_ascii_case!(s, b"-") {
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let parts: Vec<&str> = from_bytes(&s[1..]).split(' ').collect();
|
||||
let digits_in_date = 10;
|
||||
match parts.len() {
|
||||
1 => {
|
||||
if parts[0].len() == digits_in_date {
|
||||
let date = parse_modifier_date(parts[0])?;
|
||||
Ok(Modifier::DateOffset {
|
||||
years: sign * date.year(),
|
||||
months: sign * date.month() as i32,
|
||||
days: sign * date.day() as i32,
|
||||
})
|
||||
} else {
|
||||
// time values are either 12, 8 or 5 digits
|
||||
let time = parse_modifier_time(parts[0])?;
|
||||
let time_delta = sign * (time.num_seconds_from_midnight() as i32);
|
||||
Ok(Modifier::TimeOffset(TimeDelta::seconds(time_delta.into())))
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let date = parse_modifier_date(parts[0])?;
|
||||
let time = parse_modifier_time(parts[1])?;
|
||||
// Convert time to total seconds (with sign)
|
||||
let time_delta = sign * (time.num_seconds_from_midnight() as i32);
|
||||
Ok(Modifier::DateTimeOffset {
|
||||
years: sign * (date.year()),
|
||||
months: sign * (date.month() as i32),
|
||||
days: sign * date.day() as i32,
|
||||
seconds: time_delta,
|
||||
})
|
||||
}
|
||||
_ => Err(InvalidModifier(
|
||||
"Invalid date/time offset format".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(InvalidModifier(
|
||||
"Invalid date/time offset format".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(InvalidModifier(
|
||||
"Invalid date/time offset format".to_string(),
|
||||
)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn exec_timediff(values: &[Register]) -> Value {
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use turso_macros::match_ignore_ascii_case;
|
||||
|
||||
/// Tracks computation counts to verify incremental behavior (for tests now), and in the future
|
||||
/// should be used to provide statistics.
|
||||
@@ -936,8 +937,9 @@ impl ProjectOperator {
|
||||
}
|
||||
}
|
||||
Expr::FunctionCall { name, args, .. } => {
|
||||
match name.as_str().to_lowercase().as_str() {
|
||||
"hex" => {
|
||||
let name_bytes = name.as_str().as_bytes();
|
||||
match_ignore_ascii_case!(match name_bytes {
|
||||
b"hex" => {
|
||||
if args.len() == 1 {
|
||||
let arg_val = self.evaluate_expression(&args[0], values);
|
||||
match arg_val {
|
||||
@@ -949,7 +951,7 @@ impl ProjectOperator {
|
||||
}
|
||||
}
|
||||
_ => Value::Null, // Other functions not supported yet
|
||||
}
|
||||
})
|
||||
}
|
||||
Expr::Parenthesized(inner) => {
|
||||
assert!(
|
||||
|
||||
@@ -20,7 +20,9 @@ use crate::result::LimboResult;
|
||||
use crate::storage::btree::BTreeCursor;
|
||||
use crate::translate::collate::CollationSeq;
|
||||
use crate::translate::plan::SelectPlan;
|
||||
use crate::util::{module_args_from_sql, module_name_from_sql, IOExt, UnparsedFromSqlIndex};
|
||||
use crate::util::{
|
||||
module_args_from_sql, module_name_from_sql, type_from_name, IOExt, UnparsedFromSqlIndex,
|
||||
};
|
||||
use crate::{return_if_io, LimboError, MvCursor, Pager, RefValue, SymbolTable, VirtualTable};
|
||||
use crate::{util::normalize_ident, Result};
|
||||
use core::fmt;
|
||||
@@ -904,37 +906,10 @@ fn create_table(
|
||||
|
||||
let mut typename_exactly_integer = false;
|
||||
let ty = match col_type {
|
||||
Some(data_type) => 'ty: {
|
||||
// https://www.sqlite.org/datatype3.html
|
||||
let mut type_name = data_type.name.clone();
|
||||
type_name.make_ascii_uppercase();
|
||||
|
||||
if type_name.is_empty() {
|
||||
break 'ty Type::Blob;
|
||||
}
|
||||
|
||||
if type_name == "INTEGER" {
|
||||
typename_exactly_integer = true;
|
||||
break 'ty Type::Integer;
|
||||
}
|
||||
|
||||
if let Some(ty) = type_name.as_bytes().windows(3).find_map(|s| match s {
|
||||
b"INT" => Some(Type::Integer),
|
||||
_ => None,
|
||||
}) {
|
||||
break 'ty ty;
|
||||
}
|
||||
|
||||
if let Some(ty) = type_name.as_bytes().windows(4).find_map(|s| match s {
|
||||
b"CHAR" | b"CLOB" | b"TEXT" => Some(Type::Text),
|
||||
b"BLOB" => Some(Type::Blob),
|
||||
b"REAL" | b"FLOA" | b"DOUB" => Some(Type::Real),
|
||||
_ => None,
|
||||
}) {
|
||||
break 'ty ty;
|
||||
}
|
||||
|
||||
Type::Numeric
|
||||
Some(data_type) => {
|
||||
let (ty, ei) = type_from_name(&data_type.name);
|
||||
typename_exactly_integer = ei;
|
||||
ty
|
||||
}
|
||||
None => Type::Null,
|
||||
};
|
||||
@@ -1101,28 +1076,7 @@ impl From<&ColumnDefinition> for Column {
|
||||
}
|
||||
|
||||
let ty = match value.col_type {
|
||||
Some(ref data_type) => {
|
||||
// https://www.sqlite.org/datatype3.html
|
||||
let type_name = data_type.name.clone().to_uppercase();
|
||||
|
||||
if type_name.contains("INT") {
|
||||
Type::Integer
|
||||
} else if type_name.contains("CHAR")
|
||||
|| type_name.contains("CLOB")
|
||||
|| type_name.contains("TEXT")
|
||||
{
|
||||
Type::Text
|
||||
} else if type_name.contains("BLOB") || type_name.is_empty() {
|
||||
Type::Blob
|
||||
} else if type_name.contains("REAL")
|
||||
|| type_name.contains("FLOA")
|
||||
|| type_name.contains("DOUB")
|
||||
{
|
||||
Type::Real
|
||||
} else {
|
||||
Type::Numeric
|
||||
}
|
||||
}
|
||||
Some(ref data_type) => type_from_name(&data_type.name).0,
|
||||
None => Type::Null,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::{LimboError, Result};
|
||||
use aegis::aegis256::Aegis256;
|
||||
use aes_gcm::aead::{AeadCore, OsRng};
|
||||
use std::ops::Deref;
|
||||
use turso_macros::match_ignore_ascii_case;
|
||||
// AEGIS-256 supports both 16 and 32 byte tags, we use the 16 byte variant, it is faster
|
||||
// and provides sufficient security for our use case.
|
||||
const AEGIS_TAG_SIZE: usize = 16;
|
||||
@@ -267,13 +268,14 @@ impl TryFrom<&str> for CipherMode {
|
||||
type Error = LimboError;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"aes256gcm" | "aes-256-gcm" | "aes_256_gcm" => Ok(CipherMode::Aes256Gcm),
|
||||
"aegis256" | "aegis-256" | "aegis_256" => Ok(CipherMode::Aegis256),
|
||||
let s_bytes = s.as_bytes();
|
||||
match_ignore_ascii_case!(match s_bytes {
|
||||
b"aes256gcm" | b"aes-256-gcm" | b"aes_256_gcm" => Ok(CipherMode::Aes256Gcm),
|
||||
b"aegis256" | b"aegis-256" | b"aegis_256" => Ok(CipherMode::Aegis256),
|
||||
_ => Err(LimboError::InvalidArgument(format!(
|
||||
"Unknown cipher name: {s}"
|
||||
))),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::{LimboError, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
use turso_macros::match_ignore_ascii_case;
|
||||
use turso_parser::ast;
|
||||
|
||||
/// Result type for preprocessing aggregate expressions
|
||||
@@ -1400,19 +1401,20 @@ impl<'a> LogicalPlanBuilder<'a> {
|
||||
|
||||
/// Parse aggregate function name (considering argument count for min/max)
|
||||
fn parse_aggregate_function(name: &str, arg_count: usize) -> Option<AggregateFunction> {
|
||||
match name.to_uppercase().as_str() {
|
||||
"COUNT" => Some(AggFunc::Count),
|
||||
"SUM" => Some(AggFunc::Sum),
|
||||
"AVG" => Some(AggFunc::Avg),
|
||||
let name_bytes = name.as_bytes();
|
||||
match_ignore_ascii_case!(match name_bytes {
|
||||
b"COUNT" => Some(AggFunc::Count),
|
||||
b"SUM" => Some(AggFunc::Sum),
|
||||
b"AVG" => Some(AggFunc::Avg),
|
||||
// MIN and MAX are only aggregates with 1 argument
|
||||
// With 2+ arguments, they're scalar functions
|
||||
"MIN" if arg_count == 1 => Some(AggFunc::Min),
|
||||
"MAX" if arg_count == 1 => Some(AggFunc::Max),
|
||||
"GROUP_CONCAT" => Some(AggFunc::GroupConcat),
|
||||
"STRING_AGG" => Some(AggFunc::StringAgg),
|
||||
"TOTAL" => Some(AggFunc::Total),
|
||||
b"MIN" if arg_count == 1 => Some(AggFunc::Min),
|
||||
b"MAX" if arg_count == 1 => Some(AggFunc::Max),
|
||||
b"GROUP_CONCAT" => Some(AggFunc::GroupConcat),
|
||||
b"STRING_AGG" => Some(AggFunc::StringAgg),
|
||||
b"TOTAL" => Some(AggFunc::Total),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Check if expression contains aggregates
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use chrono::Datelike;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use turso_macros::match_ignore_ascii_case;
|
||||
use turso_parser::ast::{self, ColumnDefinition, Expr, Literal, Name};
|
||||
use turso_parser::ast::{PragmaName, QualifiedName};
|
||||
|
||||
@@ -213,17 +214,17 @@ fn update_pragma(
|
||||
PragmaName::AutoVacuum => {
|
||||
let auto_vacuum_mode = match value {
|
||||
Expr::Name(name) => {
|
||||
let name = name.as_str().to_lowercase();
|
||||
match name.as_str() {
|
||||
"none" => 0,
|
||||
"full" => 1,
|
||||
"incremental" => 2,
|
||||
let name = name.as_str().as_bytes();
|
||||
match_ignore_ascii_case!(match name {
|
||||
b"none" => 0,
|
||||
b"full" => 1,
|
||||
b"incremental" => 2,
|
||||
_ => {
|
||||
return Err(LimboError::InvalidArgument(
|
||||
"invalid auto vacuum mode".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
return Err(LimboError::InvalidArgument(
|
||||
@@ -330,11 +331,11 @@ fn update_pragma(
|
||||
|
||||
let mode = match value {
|
||||
Expr::Name(name) => {
|
||||
let name_upper = name.as_str().to_uppercase();
|
||||
match name_upper.as_str() {
|
||||
"OFF" | "FALSE" | "NO" | "0" => SyncMode::Off,
|
||||
let name_bytes = name.as_str().as_bytes();
|
||||
match_ignore_ascii_case!(match name_bytes {
|
||||
b"OFF" | b"FALSE" | b"NO" | b"0" => SyncMode::Off,
|
||||
_ => SyncMode::Full,
|
||||
}
|
||||
})
|
||||
}
|
||||
Expr::Literal(Literal::Numeric(n)) => match n.as_str() {
|
||||
"0" => SyncMode::Off,
|
||||
@@ -574,8 +575,11 @@ fn query_pragma(
|
||||
ast::Expr::Literal(Literal::Numeric(i)) => i.parse::<i64>().unwrap() != 0,
|
||||
ast::Expr::Literal(Literal::String(ref s))
|
||||
| ast::Expr::Name(Name::Ident(ref s)) => {
|
||||
let s = s.to_lowercase();
|
||||
s == "1" || s == "on" || s == "true"
|
||||
let s = s.as_bytes();
|
||||
match_ignore_ascii_case!(match s {
|
||||
b"1" | b"on" | b"true" => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
return Err(LimboError::ParseError(format!(
|
||||
|
||||
104
core/util.rs
104
core/util.rs
@@ -13,6 +13,7 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tracing::{instrument, Level};
|
||||
use turso_macros::match_ignore_ascii_case;
|
||||
use turso_parser::ast::{
|
||||
self, fmt::ToTokens, Cmd, CreateTableBody, Expr, FunctionTail, Literal, Stmt, UnaryOperator,
|
||||
};
|
||||
@@ -31,6 +32,58 @@ macro_rules! io_yield_many {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! eq_ignore_ascii_case {
|
||||
( $var:expr, $value:literal ) => {{
|
||||
match_ignore_ascii_case!(match $var {
|
||||
$value => true,
|
||||
_ => false,
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! contains_ignore_ascii_case {
|
||||
( $var:expr, $value:literal ) => {{
|
||||
let compare_to_idx = $var.len().saturating_sub($value.len());
|
||||
if $var.len() < $value.len() {
|
||||
false
|
||||
} else {
|
||||
let mut result = false;
|
||||
for i in 0..=compare_to_idx {
|
||||
if eq_ignore_ascii_case!(&$var[i..i + $value.len()], $value) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! starts_with_ignore_ascii_case {
|
||||
( $var:expr, $value:literal ) => {{
|
||||
if $var.len() < $value.len() {
|
||||
false
|
||||
} else {
|
||||
eq_ignore_ascii_case!(&$var[..$value.len()], $value)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ends_with_ignore_ascii_case {
|
||||
( $var:expr, $value:literal ) => {{
|
||||
if $var.len() < $value.len() {
|
||||
false
|
||||
} else {
|
||||
eq_ignore_ascii_case!(&$var[$var.len() - $value.len()..], $value)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub trait IOExt {
|
||||
fn block<T>(&self, f: impl FnMut() -> Result<IOResult<T>>) -> Result<T>;
|
||||
}
|
||||
@@ -110,14 +163,14 @@ pub fn parse_schema_rows(
|
||||
StepResult::Row => {
|
||||
let row = rows.row().unwrap();
|
||||
let ty = row.get::<&str>(0)?;
|
||||
if !["table", "index", "view"].contains(&ty) {
|
||||
continue;
|
||||
}
|
||||
match ty {
|
||||
"table" => {
|
||||
let root_page: i64 = row.get::<i64>(3)?;
|
||||
let sql: &str = row.get::<&str>(4)?;
|
||||
if root_page == 0 && sql.to_lowercase().contains("create virtual") {
|
||||
let sql_bytes = sql.as_bytes();
|
||||
if root_page == 0
|
||||
&& contains_ignore_ascii_case!(sql_bytes, b"create virtual")
|
||||
{
|
||||
let name: &str = row.get::<&str>(1)?;
|
||||
// a virtual table is found in the sqlite_schema, but it's no
|
||||
// longer in the in-memory schema. We need to recreate it if
|
||||
@@ -614,6 +667,36 @@ pub fn exprs_are_equivalent(expr1: &Expr, expr2: &Expr) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
// this function returns the affinity type and whether the type name was exactly "INTEGER"
|
||||
// https://www.sqlite.org/datatype3.html
|
||||
pub(crate) fn type_from_name(type_name: &str) -> (Type, bool) {
|
||||
let type_name = type_name.as_bytes();
|
||||
if type_name.is_empty() {
|
||||
return (Type::Blob, false);
|
||||
}
|
||||
|
||||
if eq_ignore_ascii_case!(type_name, b"INTEGER") {
|
||||
return (Type::Integer, true);
|
||||
}
|
||||
|
||||
if let Some(ty) = type_name.windows(4).find_map(|s| {
|
||||
if contains_ignore_ascii_case!(s, b"INT") {
|
||||
return Some(Type::Integer);
|
||||
}
|
||||
|
||||
match_ignore_ascii_case!(match s {
|
||||
b"CHAR" | b"CLOB" | b"TEXT" => Some(Type::Text),
|
||||
b"BLOB" => Some(Type::Blob),
|
||||
b"REAL" | b"FLOA" | b"DOUB" => Some(Type::Real),
|
||||
_ => None,
|
||||
})
|
||||
}) {
|
||||
return (ty, false);
|
||||
}
|
||||
|
||||
(Type::Numeric, false)
|
||||
}
|
||||
|
||||
pub fn columns_from_create_table_body(
|
||||
body: &turso_parser::ast::CreateTableBody,
|
||||
) -> crate::Result<Vec<Column>> {
|
||||
@@ -710,15 +793,16 @@ impl From<&str> for CacheMode {
|
||||
|
||||
impl OpenMode {
|
||||
pub fn from_str(s: &str) -> Result<Self> {
|
||||
match s.trim().to_lowercase().as_str() {
|
||||
"ro" => Ok(OpenMode::ReadOnly),
|
||||
"rw" => Ok(OpenMode::ReadWrite),
|
||||
"memory" => Ok(OpenMode::Memory),
|
||||
"rwc" => Ok(OpenMode::ReadWriteCreate),
|
||||
let s_bytes = s.trim().as_bytes();
|
||||
match_ignore_ascii_case!(match s_bytes {
|
||||
b"ro" => Ok(OpenMode::ReadOnly),
|
||||
b"rw" => Ok(OpenMode::ReadWrite),
|
||||
b"memory" => Ok(OpenMode::Memory),
|
||||
b"rwc" => Ok(OpenMode::ReadWriteCreate),
|
||||
_ => Err(LimboError::InvalidArgument(format!(
|
||||
"Invalid mode: '{s}'. Expected one of 'ro', 'rw', 'memory', 'rwc'"
|
||||
))),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ use std::{
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use turso_macros::match_ignore_ascii_case;
|
||||
|
||||
use crate::{pseudo::PseudoCursor, result::LimboResult};
|
||||
|
||||
@@ -8831,11 +8832,11 @@ pub fn op_journal_mode(
|
||||
// Currently, Turso only supports WAL mode
|
||||
// If a new mode is specified, we validate it but always return "wal"
|
||||
if let Some(mode) = new_mode {
|
||||
let mode_lower = mode.to_lowercase();
|
||||
let mode_bytes = mode.as_bytes();
|
||||
// Valid journal modes in SQLite are: delete, truncate, persist, memory, wal, off
|
||||
// We accept any valid mode but always use WAL
|
||||
match mode_lower.as_str() {
|
||||
"delete" | "truncate" | "persist" | "memory" | "wal" | "off" => {
|
||||
match_ignore_ascii_case!(match mode_bytes {
|
||||
b"delete" | b"truncate" | b"persist" | b"memory" | b"wal" | b"off" => {
|
||||
// Mode is valid, but we stay in WAL mode
|
||||
}
|
||||
_ => {
|
||||
@@ -8844,7 +8845,7 @@ pub fn op_journal_mode(
|
||||
"Unknown journal mode: {mode}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Always return "wal" as the current journal mode
|
||||
|
||||
@@ -112,8 +112,14 @@ pub fn match_ignore_ascci_case(input: TokenStream) -> TokenStream {
|
||||
entry: &PathEntry,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let eof_handle = if let Some(ref result) = entry.result {
|
||||
let guard = if let Some(ref b) = result.guard {
|
||||
let expr = &b.1;
|
||||
quote! { if #expr }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let body = &result.body;
|
||||
quote! { None => { #body } }
|
||||
quote! { None #guard => { #body } }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user