1+ import sqlite3
2+ import os
3+ import platform
4+ import re
5+ import sqlparse
6+ from sqlparse .tokens import Name
7+ from os .path import expanduser
8+ from .pgliterals .main import get_literals
9+
10+
11+ white_space_regex = re .compile ("\\ s+" , re .MULTILINE )
12+
13+
14+ def _compile_regex (keyword ):
15+ # Surround the keyword with word boundaries and replace interior whitespace
16+ # with whitespace wildcards
17+ pattern = "\\ b" + white_space_regex .sub (r"\\s+" , keyword ) + "\\ b"
18+ return re .compile (pattern , re .MULTILINE | re .IGNORECASE )
19+
20+
21+ keywords = get_literals ("keywords" )
22+ keyword_regexs = {kw : _compile_regex (kw ) for kw in keywords }
23+
24+
25+ def history_freq_location ():
26+ """Return the path to the history frequency database location."""
27+ if "XDG_DATA_HOME" in os .environ :
28+ return "%s/pgcli/history_freq.db" % expanduser (os .environ ["XDG_DATA_HOME" ])
29+ elif platform .system () == "Windows" :
30+ return os .getenv ("USERPROFILE" ) + "\\ AppData\\ Local\\ dbcli\\ pgcli\\ history_freq.db"
31+ else :
32+ return expanduser ("~/.local/share/pgcli/history_freq.db" )
33+
34+
35+ class HistoryFrequency :
36+ def __init__ (self , db_path = None ):
37+ """Initialize the history frequency tracker.
38+ :param db_path: path to the SQLite database file.
39+ """
40+ self .db_path = db_path or history_freq_location ()
41+ self .conn = None
42+
43+ # For in-memory databases, we need to keep the connection open
44+ if self .db_path == ":memory:" :
45+ self .conn = sqlite3 .connect (self .db_path )
46+ self ._create_table (self .conn )
47+ else :
48+ self ._create_table ()
49+
50+ def _create_table (self , conn = None ):
51+ """Create the frequency tables if they don't exist."""
52+ # Ensure directory exists (skip for in-memory databases)
53+ if self .db_path != ":memory:" :
54+ os .makedirs (os .path .dirname (self .db_path ), exist_ok = True )
55+
56+ if conn is None :
57+ conn = sqlite3 .connect (self .db_path )
58+ with conn :
59+ self ._create_tables_on_conn (conn )
60+ conn .close ()
61+ else :
62+ self ._create_tables_on_conn (conn )
63+
64+ def _create_tables_on_conn (self , conn ):
65+ """Create tables on the given connection."""
66+ conn .execute ("""
67+ CREATE TABLE IF NOT EXISTS keyword_frequency (
68+ keyword TEXT PRIMARY KEY,
69+ frequency INTEGER DEFAULT 1,
70+ last_used TIMESTAMP DEFAULT CURRENT_TIMESTAMP
71+ )
72+ """ )
73+ conn .execute ("""
74+ CREATE TABLE IF NOT EXISTS name_frequency (
75+ name TEXT PRIMARY KEY,
76+ frequency INTEGER DEFAULT 1,
77+ last_used TIMESTAMP DEFAULT CURRENT_TIMESTAMP
78+ )
79+ """ )
80+
81+ def _get_connection (self ):
82+ """Get a database connection."""
83+ if self .db_path == ":memory:" :
84+ return self .conn
85+ return sqlite3 .connect (self .db_path )
86+
87+ def _close_connection (self , conn ):
88+ """Close the connection unless it's an in-memory database."""
89+ if self .db_path != ":memory:" :
90+ conn .close ()
91+
92+ def increment_keyword (self , keyword ):
93+ """Increment the frequency count for a keyword."""
94+ keyword = keyword .lower ()
95+ conn = self ._get_connection ()
96+ with conn :
97+ conn .execute ("""
98+ INSERT OR REPLACE INTO keyword_frequency (keyword, frequency, last_used)
99+ VALUES (
100+ ?,
101+ COALESCE((SELECT frequency FROM keyword_frequency WHERE keyword = ?), 0) + 1,
102+ CURRENT_TIMESTAMP
103+ )
104+ """ , (keyword , keyword ))
105+ self ._close_connection (conn )
106+
107+ def increment_name (self , name ):
108+ """Increment the frequency count for a name/identifier."""
109+ name = name .lower ()
110+ conn = self ._get_connection ()
111+ with conn :
112+ conn .execute ("""
113+ INSERT OR REPLACE INTO name_frequency (name, frequency, last_used)
114+ VALUES (
115+ ?,
116+ COALESCE((SELECT frequency FROM name_frequency WHERE name = ?), 0) + 1,
117+ CURRENT_TIMESTAMP
118+ )
119+ """ , (name , name ))
120+ self ._close_connection (conn )
121+
122+ def get_keyword_frequency (self , keyword ):
123+ """Get the frequency count for a keyword."""
124+ keyword = keyword .lower ()
125+ conn = self ._get_connection ()
126+ cursor = conn .cursor ()
127+ cursor .execute ("SELECT frequency FROM keyword_frequency WHERE keyword = ?" , (keyword ,))
128+ result = cursor .fetchone ()
129+ self ._close_connection (conn )
130+ return result [0 ] if result else 0
131+
132+ def get_name_frequency (self , name ):
133+ """Get the frequency count for a name/identifier."""
134+ name = name .lower ()
135+ conn = self ._get_connection ()
136+ cursor = conn .cursor ()
137+ cursor .execute ("SELECT frequency FROM name_frequency WHERE name = ?" , (name ,))
138+ result = cursor .fetchone ()
139+ self ._close_connection (conn )
140+ return result [0 ] if result else 0
141+
142+ def update_from_text (self , text ):
143+ """Update frequencies from SQL text by extracting keywords and names."""
144+ self .update_keywords (text )
145+ self .update_names (text )
146+
147+ def update_keywords (self , text ):
148+ """Update keyword frequencies from SQL text."""
149+ for keyword , regex in keyword_regexs .items ():
150+ count = len (list (regex .finditer (text )))
151+ if count > 0 :
152+ keyword_lower = keyword .lower ()
153+ conn = self ._get_connection ()
154+ with conn :
155+ conn .execute ("""
156+ INSERT OR REPLACE INTO keyword_frequency (keyword, frequency, last_used)
157+ VALUES (
158+ ?,
159+ COALESCE((SELECT frequency FROM keyword_frequency WHERE keyword = ?), 0) + ?,
160+ CURRENT_TIMESTAMP
161+ )
162+ """ , (keyword_lower , keyword_lower , count ))
163+ self ._close_connection (conn )
164+
165+ def update_names (self , text ):
166+ """Update name/identifier frequencies from SQL text."""
167+ for parsed in sqlparse .parse (text ):
168+ for token in parsed .flatten ():
169+ if token .ttype in Name :
170+ name_lower = token .value .lower ()
171+ conn = self ._get_connection ()
172+ with conn :
173+ conn .execute ("""
174+ INSERT OR REPLACE INTO name_frequency (name, frequency, last_used)
175+ VALUES (
176+ ?,
177+ COALESCE((SELECT frequency FROM name_frequency WHERE name = ?), 0) + 1,
178+ CURRENT_TIMESTAMP
179+ )
180+ """ , (name_lower , name_lower ))
181+ self ._close_connection (conn )
182+
183+ def clear (self ):
184+ """Clear all frequency data."""
185+ conn = self ._get_connection ()
186+ with conn :
187+ conn .execute ("DELETE FROM keyword_frequency" )
188+ conn .execute ("DELETE FROM name_frequency" )
189+ self ._close_connection (conn )
0 commit comments