33use std:: collections:: { HashMap , HashSet } ;
44use std:: os:: raw:: c_char;
55use std:: ptr;
6- use std:: sync:: { Arc , Mutex , OnceLock } ;
6+ use std:: sync:: Arc ;
77
88use ext_php_rs:: convert:: { FromZval , IntoZval , IntoZvalDyn } ;
99use ext_php_rs:: exception:: { PhpException , PhpResult } ;
@@ -915,12 +915,6 @@ struct Rule {
915915 is_fragment : bool ,
916916}
917917
918- #[ derive( Clone , Copy , Eq , Hash , PartialEq ) ]
919- struct GrammarCacheKey {
920- low : u64 ,
921- high : u64 ,
922- }
923-
924918impl Grammar {
925919 fn rule ( & self , rule_id : i64 ) -> Option < & Rule > {
926920 usize:: try_from ( rule_id)
@@ -930,10 +924,11 @@ impl Grammar {
930924 }
931925}
932926
933- // Cache only Rust-owned grammar data, keyed by exported grammar content. Zend
934- // object handles are request-local and can be reused, so they must not identify
935- // cached entries.
936- static GRAMMAR_CACHE : OnceLock < Mutex < HashMap < GrammarCacheKey , Arc < Grammar > > > > = OnceLock :: new ( ) ;
927+ #[ php_class]
928+ #[ php( name = "WP_MySQL_Native_Grammar" ) ]
929+ pub struct WpMySqlNativeGrammar {
930+ grammar : Arc < Grammar > ,
931+ }
937932
938933enum ParserTokenSource {
939934 Php ( Vec < Zval > ) ,
@@ -1675,23 +1670,17 @@ impl WpMySqlNativeParser {
16751670}
16761671
16771672fn export_grammar ( grammar_zval : & mut Zval ) -> PhpResult < Arc < Grammar > > {
1673+ if let Some ( cached) = cached_native_grammar ( grammar_zval) ? {
1674+ return Ok ( cached) ;
1675+ }
1676+
16781677 let exported = php_function ( "wp_sqlite_mysql_native_export_grammar" ) ?
16791678 . try_call ( vec ! [ & * grammar_zval as & dyn IntoZvalDyn ] )
16801679 . map_err ( php_error) ?;
16811680 let array = exported
16821681 . array ( )
16831682 . ok_or_else ( || php_error ( "Exported grammar must be an array" ) ) ?;
16841683
1685- let cache_key = grammar_cache_key ( array) ?;
1686- if let Some ( cached) = GRAMMAR_CACHE
1687- . get_or_init ( || Mutex :: new ( HashMap :: new ( ) ) )
1688- . lock ( )
1689- . map_err ( |_| php_error ( "Grammar cache lock poisoned" ) ) ?
1690- . get ( & cache_key)
1691- {
1692- return Ok ( Arc :: clone ( cached) ) ;
1693- }
1694-
16951684 let highest_terminal_id = array
16961685 . get ( "highest_terminal_id" )
16971686 . and_then ( Zval :: long)
@@ -1741,130 +1730,37 @@ fn export_grammar(grammar_zval: &mut Zval) -> PhpResult<Arc<Grammar>> {
17411730 select_statement_rule_id,
17421731 } ) ;
17431732
1744- GRAMMAR_CACHE
1745- . get_or_init ( || Mutex :: new ( HashMap :: new ( ) ) )
1746- . lock ( )
1747- . map_err ( |_| php_error ( "Grammar cache lock poisoned" ) ) ?
1748- . insert ( cache_key, Arc :: clone ( & grammar) ) ;
1733+ cache_native_grammar ( grammar_zval, Arc :: clone ( & grammar) ) ?;
17491734
17501735 Ok ( grammar)
17511736}
17521737
1753- fn grammar_cache_key ( array : & ZendHashTable ) -> PhpResult < GrammarCacheKey > {
1754- let mut hasher = GrammarCacheHasher :: new ( ) ;
1755- hash_grammar_array ( & mut hasher, array) ?;
1756- Ok ( hasher. finish ( ) )
1757- }
1758-
1759- struct GrammarCacheHasher {
1760- low : u64 ,
1761- high : u64 ,
1762- }
1763-
1764- impl GrammarCacheHasher {
1765- fn new ( ) -> Self {
1766- Self {
1767- low : 0xcbf29ce484222325 ,
1768- high : 0x6c62272e07bb0142 ,
1769- }
1770- }
1771-
1772- fn write_byte ( & mut self , byte : u8 ) {
1773- self . low ^= u64:: from ( byte) ;
1774- self . low = self . low . wrapping_mul ( 0x100000001b3 ) ;
1775- self . high ^= u64:: from ( byte) . rotate_left ( 5 ) ;
1776- self . high = self . high . wrapping_mul ( 0x100000001b3 ^ 0x9e3779b97f4a7c15 ) ;
1777- }
1778-
1779- fn write_bytes ( & mut self , bytes : & [ u8 ] ) {
1780- self . write_usize ( bytes. len ( ) ) ;
1781- for byte in bytes {
1782- self . write_byte ( * byte) ;
1783- }
1784- }
1785-
1786- fn write_i64 ( & mut self , value : i64 ) {
1787- for byte in value. to_le_bytes ( ) {
1788- self . write_byte ( byte) ;
1789- }
1790- }
1791-
1792- fn write_u64 ( & mut self , value : u64 ) {
1793- for byte in value. to_le_bytes ( ) {
1794- self . write_byte ( byte) ;
1795- }
1796- }
1797-
1798- fn write_usize ( & mut self , value : usize ) {
1799- self . write_u64 ( value as u64 ) ;
1800- }
1801-
1802- fn finish ( self ) -> GrammarCacheKey {
1803- GrammarCacheKey {
1804- low : self . low ,
1805- high : self . high ,
1806- }
1807- }
1808- }
1809-
1810- fn hash_grammar_array ( hasher : & mut GrammarCacheHasher , array : & ZendHashTable ) -> PhpResult < ( ) > {
1811- hasher. write_usize ( array. len ( ) ) ;
1812- for ( key, value) in array {
1813- hash_grammar_array_key ( hasher, key) ;
1814- hash_grammar_zval ( hasher, value) ?;
1815- }
1816- Ok ( ( ) )
1817- }
1818-
1819- fn hash_grammar_zval ( hasher : & mut GrammarCacheHasher , zval : & Zval ) -> PhpResult < ( ) > {
1820- let zval = zval. dereference ( ) ;
1821- match zval. get_type ( ) {
1822- DataType :: Null => hasher. write_byte ( 0 ) ,
1823- DataType :: False => hasher. write_byte ( 1 ) ,
1824- DataType :: True => hasher. write_byte ( 2 ) ,
1825- DataType :: Long => {
1826- hasher. write_byte ( 3 ) ;
1827- hasher. write_i64 (
1828- zval. long ( )
1829- . ok_or_else ( || php_error ( "Grammar integer value is invalid" ) ) ?,
1830- ) ;
1831- }
1832- DataType :: String => {
1833- hasher. write_byte ( 4 ) ;
1834- hasher. write_bytes (
1835- zval. str ( )
1836- . ok_or_else ( || php_error ( "Grammar string value is invalid" ) ) ?
1837- . as_bytes ( ) ,
1838- ) ;
1839- }
1840- DataType :: Array => {
1841- hasher. write_byte ( 5 ) ;
1842- let array = zval
1843- . array ( )
1844- . ok_or_else ( || php_error ( "Grammar array value is invalid" ) ) ?;
1845- hash_grammar_array ( hasher, array) ?;
1846- }
1847- _ => return Err ( php_error ( "Unsupported grammar cache value" ) ) ,
1848- }
1738+ fn cached_native_grammar ( grammar : & Zval ) -> PhpResult < Option < Arc < Grammar > > > {
1739+ let object = grammar
1740+ . object ( )
1741+ . ok_or_else ( || php_error ( "Parser grammar must be an object" ) ) ?;
1742+ let properties = object. get_properties ( ) . map_err ( php_error) ?;
1743+ let Some ( native_grammar) = properties. get ( "native_grammar" ) else {
1744+ return Ok ( None ) ;
1745+ } ;
1746+ let Some ( native_grammar) = <& WpMySqlNativeGrammar as FromZval >:: from_zval ( native_grammar)
1747+ else {
1748+ return Ok ( None ) ;
1749+ } ;
18491750
1850- Ok ( ( ) )
1751+ Ok ( Some ( Arc :: clone ( & native_grammar . grammar ) ) )
18511752}
18521753
1853- fn hash_grammar_array_key ( hasher : & mut GrammarCacheHasher , key : ArrayKey < ' _ > ) {
1854- match key {
1855- ArrayKey :: Long ( value) => {
1856- hasher. write_byte ( 0 ) ;
1857- hasher. write_i64 ( value) ;
1858- }
1859- ArrayKey :: String ( value) => {
1860- hasher. write_byte ( 1 ) ;
1861- hasher. write_bytes ( value. as_bytes ( ) ) ;
1862- }
1863- ArrayKey :: Str ( value) => {
1864- hasher. write_byte ( 1 ) ;
1865- hasher. write_bytes ( value. as_bytes ( ) ) ;
1866- }
1867- }
1754+ fn cache_native_grammar ( grammar_zval : & mut Zval , grammar : Arc < Grammar > ) -> PhpResult < ( ) > {
1755+ let object = grammar_zval
1756+ . object_mut ( )
1757+ . ok_or_else ( || php_error ( "Parser grammar must be an object" ) ) ?;
1758+ let native_grammar = WpMySqlNativeGrammar { grammar }
1759+ . into_zval ( false )
1760+ . map_err ( php_error) ?;
1761+ object
1762+ . set_property ( "native_grammar" , native_grammar)
1763+ . map_err ( php_error)
18681764}
18691765
18701766fn export_tokens ( tokens : & mut Zval ) -> PhpResult < ( ParserTokenSource , Vec < i64 > ) > {
@@ -2018,6 +1914,7 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) {
20181914#[ php_module]
20191915pub fn get_module ( module : ModuleBuilder ) -> ModuleBuilder {
20201916 module
1917+ . class :: < WpMySqlNativeGrammar > ( )
20211918 . class :: < WpMySqlNativeAst > ( )
20221919 . class :: < WpMySqlNativeTokenStream > ( )
20231920 . class :: < WpMySqlNativeLexer > ( )
0 commit comments