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 6659 : fn is_enum_node(node_type: NodeType) -> bool {
17 6659 : let id = node_type.0;
18 6659 : id > NodeType::ENUM_KEYS_.0 && id < NodeType::OBJECT_KEYS_.0
19 6659 : }
20 :
21 4018 : fn append_text(out: &mut Vec<u8>, text: &[u8]) {
22 4018 : out.extend_from_slice(&(text.len() as u64).to_le_bytes());
23 4018 : out.extend_from_slice(text);
24 4018 : }
25 :
26 16436 : fn append_node(parsed: &ParsedScript, node: &Node, out: &mut Vec<u8>) {
27 16436 : let node_type = node.node_type();
28 16436 : out.extend_from_slice(&node_type.0.to_le_bytes());
29 16436 : out.extend_from_slice(&node.attribute_key().0.to_le_bytes());
30 6659 : match node_type {
31 1 : NodeType::NONE | NodeType::LITERAL_NULL => {}
32 617 : NodeType::BOOL => out.push((node.children_begin_or_value() != 0) as u8),
33 3032 : 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 986 : | NodeType::LITERAL_INTERVAL => {
39 986 : let scanned = &parsed.scanned;
40 986 : let span = resolve_text_span(&scanned.text, &scanned.symbols, *node.symbol_span());
41 986 : let bytes = scanned.text.as_bytes();
42 986 : let begin = (span.offset as usize).min(bytes.len());
43 986 : let end = (begin + span.length as usize).min(bytes.len());
44 986 : append_text(out, &bytes[begin..end]);
45 986 : }
46 5141 : NodeType::ARRAY => out.extend_from_slice(&(node.children_count()).to_le_bytes()),
47 6659 : _ if is_enum_node(node_type) => out.extend_from_slice(&node.children_begin_or_value().to_le_bytes()),
48 6241 : _ => out.extend_from_slice(&(node.children_count()).to_le_bytes()),
49 : }
50 16436 : }
51 :
52 : /// Two FNV-1a style 64 bit hashes over the bytes
53 2396 : fn hash(bytes: &[u8]) -> [u64; 2] {
54 2396 : let mut digest = [14695981039346656037u64, 7809847782465536322u64];
55 193290 : for &b in bytes {
56 193290 : digest[0] = (digest[0] ^ b as u64).wrapping_mul(1099511628211);
57 193290 : digest[1] = (digest[1] ^ b.wrapping_add(0x9d) as u64).wrapping_mul(1099511628211);
58 193290 : }
59 2396 : digest
60 2396 : }
61 :
62 : /// Compute the signature of a parsed script. Fails for scripts with scanner or parser errors.
63 1030 : pub fn compute_signature(parsed: &ParsedScript) -> Result<String> {
64 1030 : if !parsed.scanned.errors.is_empty() || !parsed.errors.is_empty() {
65 18 : return Err(Error::status(StatusCode::SCRIPT_NOT_PARSED));
66 1012 : }
67 1012 : let mut script_bytes = Vec::new();
68 1012 : script_bytes.extend_from_slice(b"DQLSIG");
69 1012 : script_bytes.push(SCRIPT_SIGNATURE_VERSION);
70 1012 : script_bytes.extend_from_slice(&(parsed.statements.len() as u32).to_le_bytes());
71 1012 : let mut statement_bytes = Vec::new();
72 1384 : for statement in &parsed.statements {
73 1384 : statement_bytes.clear();
74 1384 : let begin = statement.nodes_begin as usize;
75 16436 : for node in &parsed.nodes[begin..begin + statement.node_count as usize] {
76 16436 : append_node(parsed, node, &mut statement_bytes);
77 16436 : }
78 1384 : let digest = hash(&statement_bytes);
79 1384 : script_bytes.push(statement.statement_type.0);
80 1384 : script_bytes.extend_from_slice(&digest[0].to_le_bytes());
81 1384 : script_bytes.extend_from_slice(&digest[1].to_le_bytes());
82 : }
83 1012 : let digest = hash(&script_bytes);
84 1012 : Ok(format!("{:016x}{:016x}", digest[0], digest[1]))
85 1030 : }
|