Line data Source code
1 : //! Script signatures, the persisted cache keys of compiled queries.
2 : //!
3 : //! The signature hashes the statement types and, per statement, the node types, attribute keys,
4 : //! names and literal texts in node order. Signatures are persisted, so bump
5 : //! `SCRIPT_SIGNATURE_VERSION` when the encoding changes.
6 :
7 : use crate::error::{Error, Result};
8 : use dashql_buffers::parser::{Node, NodeType};
9 : use dashql_buffers::status::StatusCode;
10 : use dashql_parser::parser::context::resolve_text_span;
11 : use dashql_parser::parser::ParsedScript;
12 :
13 : /// The version of the signature encoding
14 : const SCRIPT_SIGNATURE_VERSION: u8 = 2;
15 :
16 6262 : fn is_enum_node(node_type: NodeType) -> bool {
17 6262 : let id = node_type.0;
18 6262 : id > NodeType::ENUM_KEYS_.0 && id < NodeType::OBJECT_KEYS_.0
19 6262 : }
20 :
21 3544 : fn append_text(out: &mut Vec<u8>, text: &[u8]) {
22 3544 : out.extend_from_slice(&(text.len() as u64).to_le_bytes());
23 3544 : out.extend_from_slice(text);
24 3544 : }
25 :
26 15287 : fn append_node(parsed: &ParsedScript, node: &Node, out: &mut Vec<u8>) {
27 15287 : let node_type = node.node_type();
28 15287 : out.extend_from_slice(&node_type.0.to_le_bytes());
29 15287 : out.extend_from_slice(&node.attribute_key().0.to_le_bytes());
30 6262 : match node_type {
31 1 : NodeType::NONE | NodeType::LITERAL_NULL => {}
32 610 : NodeType::BOOL => out.push((node.children_begin_or_value() != 0) as u8),
33 2708 : NodeType::NAME => append_text(out, parsed.scanned.names.get(node.children_begin_or_value()).text.as_bytes()),
34 : NodeType::OPERATOR
35 : | NodeType::LITERAL_INTEGER
36 : | NodeType::LITERAL_FLOAT
37 : | NodeType::LITERAL_STRING
38 836 : | NodeType::LITERAL_INTERVAL => {
39 836 : let scanned = &parsed.scanned;
40 836 : let span = resolve_text_span(&scanned.text, &scanned.symbols, *node.symbol_span());
41 836 : let bytes = scanned.text.as_bytes();
42 836 : let begin = (span.offset as usize).min(bytes.len());
43 836 : let end = (begin + span.length as usize).min(bytes.len());
44 836 : append_text(out, &bytes[begin..end]);
45 836 : }
46 4870 : NodeType::ARRAY => out.extend_from_slice(&(node.children_count()).to_le_bytes()),
47 6262 : _ if is_enum_node(node_type) => out.extend_from_slice(&node.children_begin_or_value().to_le_bytes()),
48 5851 : _ => out.extend_from_slice(&(node.children_count()).to_le_bytes()),
49 : }
50 15287 : }
51 :
52 : /// Two FNV-1a style 64 bit hashes over the bytes
53 2227 : fn hash(bytes: &[u8]) -> [u64; 2] {
54 2227 : let mut digest = [14695981039346656037u64, 7809847782465536322u64];
55 177173 : for &b in bytes {
56 177173 : digest[0] = (digest[0] ^ b as u64).wrapping_mul(1099511628211);
57 177173 : digest[1] = (digest[1] ^ b.wrapping_add(0x9d) as u64).wrapping_mul(1099511628211);
58 177173 : }
59 2227 : digest
60 2227 : }
61 :
62 : /// Compute the signature of a parsed script. Fails for scripts with scanner or parser errors.
63 963 : pub fn compute_signature(parsed: &ParsedScript) -> Result<String> {
64 963 : if !parsed.scanned.errors.is_empty() || !parsed.errors.is_empty() {
65 18 : return Err(Error::status(StatusCode::SCRIPT_NOT_PARSED));
66 945 : }
67 945 : let mut script_bytes = Vec::new();
68 945 : script_bytes.extend_from_slice(b"DQLSIG");
69 945 : script_bytes.push(SCRIPT_SIGNATURE_VERSION);
70 945 : script_bytes.extend_from_slice(&(parsed.statements.len() as u32).to_le_bytes());
71 945 : let mut statement_bytes = Vec::new();
72 1282 : for statement in &parsed.statements {
73 1282 : statement_bytes.clear();
74 1282 : let begin = statement.nodes_begin as usize;
75 15287 : for node in &parsed.nodes[begin..begin + statement.node_count as usize] {
76 15287 : append_node(parsed, node, &mut statement_bytes);
77 15287 : }
78 1282 : let digest = hash(&statement_bytes);
79 1282 : script_bytes.push(statement.statement_type.0);
80 1282 : script_bytes.extend_from_slice(&digest[0].to_le_bytes());
81 1282 : script_bytes.extend_from_slice(&digest[1].to_le_bytes());
82 : }
83 945 : let digest = hash(&script_bytes);
84 945 : Ok(format!("{:016x}{:016x}", digest[0], digest[1]))
85 963 : }
|