sqlshell 0.1.6__py3-none-any.whl → 0.1.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlshell might be problematic. Click here for more details.
- sqlshell/__init__.py +4 -2
- sqlshell/create_test_data.py +50 -0
- sqlshell/data/create_test_data.py +137 -0
- sqlshell/editor.py +355 -0
- sqlshell/main.py +1016 -656
- sqlshell/resources/__init__.py +1 -0
- sqlshell/resources/create_icon.py +53 -0
- sqlshell/resources/create_splash.py +66 -0
- sqlshell/resources/splash_screen.gif +0 -0
- sqlshell/splash_screen.py +177 -0
- sqlshell/sqlshell/create_test_data.py +4 -23
- sqlshell/sqlshell_demo.png +0 -0
- sqlshell/syntax_highlighter.py +123 -0
- sqlshell-0.1.8.dist-info/METADATA +120 -0
- sqlshell-0.1.8.dist-info/RECORD +21 -0
- {sqlshell-0.1.6.dist-info → sqlshell-0.1.8.dist-info}/WHEEL +1 -1
- sqlshell-0.1.6.dist-info/METADATA +0 -92
- sqlshell-0.1.6.dist-info/RECORD +0 -11
- {sqlshell-0.1.6.dist-info → sqlshell-0.1.8.dist-info}/entry_points.txt +0 -0
- {sqlshell-0.1.6.dist-info → sqlshell-0.1.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# SQLShell resources package initialization
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
3
|
+
|
|
4
|
+
def create_sql_icon(output_path, size=256):
|
|
5
|
+
"""Create a simple SQL icon"""
|
|
6
|
+
# Create a new image with a transparent background
|
|
7
|
+
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
|
8
|
+
draw = ImageDraw.Draw(img)
|
|
9
|
+
|
|
10
|
+
# Define colors
|
|
11
|
+
primary_color = (44, 62, 80, 255) # Dark blue-gray
|
|
12
|
+
secondary_color = (52, 152, 219, 255) # Bright blue
|
|
13
|
+
accent_color = (26, 188, 156, 255) # Teal
|
|
14
|
+
|
|
15
|
+
# Draw a rounded rectangle background
|
|
16
|
+
radius = size // 10
|
|
17
|
+
rect = [(size//8, size//8), (size - size//8, size - size//8)]
|
|
18
|
+
draw.rounded_rectangle(rect, radius, fill=primary_color)
|
|
19
|
+
|
|
20
|
+
# Try to load a font, fall back to default if not available
|
|
21
|
+
try:
|
|
22
|
+
font_size = size // 3
|
|
23
|
+
font = ImageFont.truetype("arial.ttf", font_size)
|
|
24
|
+
except IOError:
|
|
25
|
+
try:
|
|
26
|
+
font = ImageFont.truetype("DejaVuSans.ttf", font_size)
|
|
27
|
+
except IOError:
|
|
28
|
+
font = ImageFont.load_default()
|
|
29
|
+
|
|
30
|
+
# Draw SQL text
|
|
31
|
+
text = "SQL"
|
|
32
|
+
text_width, text_height = draw.textsize(text, font=font) if hasattr(draw, 'textsize') else font.getsize(text)
|
|
33
|
+
position = ((size - text_width) // 2, (size - text_height) // 2)
|
|
34
|
+
draw.text(position, text, fill=accent_color, font=font)
|
|
35
|
+
|
|
36
|
+
# Draw a small database icon
|
|
37
|
+
db_size = size // 4
|
|
38
|
+
db_x = size // 2 - db_size // 2
|
|
39
|
+
db_y = size // 2 + text_height // 2 + db_size // 2
|
|
40
|
+
|
|
41
|
+
# Draw database cylinder
|
|
42
|
+
draw.ellipse([(db_x, db_y - db_size//3), (db_x + db_size, db_y)], fill=secondary_color)
|
|
43
|
+
draw.ellipse([(db_x, db_y + db_size//3), (db_x + db_size, db_y + db_size//1.5)], fill=secondary_color)
|
|
44
|
+
draw.rectangle([(db_x, db_y), (db_x + db_size, db_y + db_size//3)], fill=secondary_color)
|
|
45
|
+
|
|
46
|
+
# Save the image
|
|
47
|
+
img.save(output_path)
|
|
48
|
+
print(f"Icon created at {output_path}")
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
output_dir = os.path.dirname(os.path.abspath(__file__))
|
|
52
|
+
output_path = os.path.join(output_dir, "icon.png")
|
|
53
|
+
create_sql_icon(output_path)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
2
|
+
import os
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
def create_frame(size, progress, text=None): # We won't use text parameter anymore
|
|
6
|
+
# Create a new image with a dark background
|
|
7
|
+
img = Image.new('RGBA', size, (44, 62, 80, 255)) # Dark blue-gray background
|
|
8
|
+
draw = ImageDraw.Draw(img)
|
|
9
|
+
|
|
10
|
+
# Calculate dimensions
|
|
11
|
+
width, height = size
|
|
12
|
+
center_x = width // 2
|
|
13
|
+
center_y = height // 2
|
|
14
|
+
|
|
15
|
+
# Create a subtle circular pattern
|
|
16
|
+
radius = 60
|
|
17
|
+
num_dots = 12
|
|
18
|
+
dot_size = 4
|
|
19
|
+
trail_length = 6
|
|
20
|
+
|
|
21
|
+
for i in range(num_dots):
|
|
22
|
+
# Calculate dot position
|
|
23
|
+
angle = (progress * 360 - i * (360 / num_dots)) % 360
|
|
24
|
+
x = center_x + radius * math.cos(math.radians(angle))
|
|
25
|
+
y = center_y + radius * math.sin(math.radians(angle))
|
|
26
|
+
|
|
27
|
+
# Calculate dot opacity based on position in trail
|
|
28
|
+
opacity = int(255 * (1 - (i / trail_length))) if i < trail_length else 0
|
|
29
|
+
|
|
30
|
+
if opacity > 0:
|
|
31
|
+
# Draw dot with gradient effect
|
|
32
|
+
for size_mult in [1.0, 0.8, 0.6]:
|
|
33
|
+
current_size = int(dot_size * size_mult)
|
|
34
|
+
current_opacity = int(opacity * size_mult)
|
|
35
|
+
draw.ellipse(
|
|
36
|
+
(x - current_size, y - current_size,
|
|
37
|
+
x + current_size, y + current_size),
|
|
38
|
+
fill=(52, 152, 219, current_opacity) # Bright blue with fading opacity
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return img
|
|
42
|
+
|
|
43
|
+
def create_splash_gif():
|
|
44
|
+
size = (400, 300)
|
|
45
|
+
frames = []
|
|
46
|
+
|
|
47
|
+
# Create 60 frames for smooth animation
|
|
48
|
+
for i in range(60):
|
|
49
|
+
progress = i / 60
|
|
50
|
+
frame = create_frame(size, progress)
|
|
51
|
+
frames.append(frame)
|
|
52
|
+
|
|
53
|
+
# Save the animated GIF
|
|
54
|
+
output_path = os.path.join(os.path.dirname(__file__), "splash_screen.gif")
|
|
55
|
+
frames[0].save(
|
|
56
|
+
output_path,
|
|
57
|
+
save_all=True,
|
|
58
|
+
append_images=frames[1:],
|
|
59
|
+
duration=50, # 50ms per frame = 20fps
|
|
60
|
+
loop=0,
|
|
61
|
+
optimize=False
|
|
62
|
+
)
|
|
63
|
+
print(f"Created splash screen GIF at: {output_path}")
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
create_splash_gif()
|
|
Binary file
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout
|
|
2
|
+
from PyQt6.QtCore import Qt, QTimer, QPropertyAnimation, QEasingCurve, QPoint, QRect, pyqtProperty
|
|
3
|
+
from PyQt6.QtGui import QPainter, QColor, QFont, QMovie, QPainterPath, QLinearGradient
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
class AnimatedSplashScreen(QWidget):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super().__init__()
|
|
9
|
+
|
|
10
|
+
# Initialize properties for animations first
|
|
11
|
+
self._opacity = 0.0
|
|
12
|
+
self._progress = 0.0
|
|
13
|
+
self.next_widget = None
|
|
14
|
+
|
|
15
|
+
# Set up the window properties
|
|
16
|
+
self.setWindowFlags(
|
|
17
|
+
Qt.WindowType.WindowStaysOnTopHint |
|
|
18
|
+
Qt.WindowType.FramelessWindowHint |
|
|
19
|
+
Qt.WindowType.SplashScreen
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Set widget attributes for proper compositing
|
|
23
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
24
|
+
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
|
|
25
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, False)
|
|
26
|
+
|
|
27
|
+
# Set fixed size
|
|
28
|
+
self.setFixedSize(400, 300)
|
|
29
|
+
|
|
30
|
+
# Center the splash screen on the screen
|
|
31
|
+
screen_geometry = self.screen().geometry()
|
|
32
|
+
self.move(
|
|
33
|
+
(screen_geometry.width() - self.width()) // 2,
|
|
34
|
+
(screen_geometry.height() - self.height()) // 2
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Create layout
|
|
38
|
+
layout = QVBoxLayout(self)
|
|
39
|
+
layout.setContentsMargins(20, 20, 20, 20)
|
|
40
|
+
layout.setSpacing(10)
|
|
41
|
+
|
|
42
|
+
# Create background container for the title and subtitle
|
|
43
|
+
self.content_container = QWidget(self)
|
|
44
|
+
content_layout = QVBoxLayout(self.content_container)
|
|
45
|
+
content_layout.setContentsMargins(20, 20, 20, 20)
|
|
46
|
+
content_layout.setSpacing(10)
|
|
47
|
+
|
|
48
|
+
# Create title label
|
|
49
|
+
self.title_label = QLabel("SQLShell", self.content_container)
|
|
50
|
+
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
51
|
+
self.title_label.setStyleSheet("""
|
|
52
|
+
QLabel {
|
|
53
|
+
color: #3498DB;
|
|
54
|
+
font-size: 32px;
|
|
55
|
+
font-weight: bold;
|
|
56
|
+
font-family: 'Segoe UI', Arial, sans-serif;
|
|
57
|
+
background: transparent;
|
|
58
|
+
}
|
|
59
|
+
""")
|
|
60
|
+
content_layout.addWidget(self.title_label)
|
|
61
|
+
|
|
62
|
+
# Create subtitle label
|
|
63
|
+
self.subtitle_label = QLabel("Loading...", self.content_container)
|
|
64
|
+
self.subtitle_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
65
|
+
self.subtitle_label.setStyleSheet("""
|
|
66
|
+
QLabel {
|
|
67
|
+
color: #2C3E50;
|
|
68
|
+
font-size: 16px;
|
|
69
|
+
font-family: 'Segoe UI', Arial, sans-serif;
|
|
70
|
+
background: transparent;
|
|
71
|
+
}
|
|
72
|
+
""")
|
|
73
|
+
content_layout.addWidget(self.subtitle_label)
|
|
74
|
+
|
|
75
|
+
# Add content container to main layout
|
|
76
|
+
layout.addWidget(self.content_container)
|
|
77
|
+
|
|
78
|
+
# Create movie label (background)
|
|
79
|
+
self.movie_label = QLabel(self)
|
|
80
|
+
self.movie_label.setGeometry(0, 0, self.width(), self.height())
|
|
81
|
+
self.movie_label.lower() # Put it at the back
|
|
82
|
+
|
|
83
|
+
# Create overlay for fade effect (between movie and content)
|
|
84
|
+
self.overlay = QLabel(self)
|
|
85
|
+
self.overlay.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
|
|
86
|
+
self.overlay.setGeometry(0, 0, self.width(), self.height())
|
|
87
|
+
self.overlay.lower() # Put it behind the content but above the movie
|
|
88
|
+
|
|
89
|
+
# Create text label for animated text
|
|
90
|
+
self.text_label = QLabel(self)
|
|
91
|
+
self.text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
92
|
+
self.text_label.setStyleSheet("color: rgba(255, 255, 255, 0); background: transparent;")
|
|
93
|
+
self.text_label.setGeometry(0, 0, self.width(), self.height())
|
|
94
|
+
self.text_label.lower() # Put it behind the content
|
|
95
|
+
|
|
96
|
+
# Create progress bar (always on top)
|
|
97
|
+
self.progress_bar = QLabel(self)
|
|
98
|
+
self.progress_bar.setFixedHeight(4)
|
|
99
|
+
self.progress_bar.setStyleSheet("background-color: #3498DB; border-radius: 2px;")
|
|
100
|
+
self.progress_bar.move(100, self.height() - 40)
|
|
101
|
+
self.progress_bar.setFixedWidth(0)
|
|
102
|
+
self.progress_bar.raise_() # Ensure it's on top
|
|
103
|
+
|
|
104
|
+
# Set up the loading animation
|
|
105
|
+
self.movie = QMovie(os.path.join(os.path.dirname(__file__), "resources", "splash_screen.gif"))
|
|
106
|
+
self.movie.setScaledSize(self.size())
|
|
107
|
+
self.movie_label.setMovie(self.movie)
|
|
108
|
+
|
|
109
|
+
# Set up fade animation
|
|
110
|
+
self.fade_anim = QPropertyAnimation(self, b"opacity")
|
|
111
|
+
self.fade_anim.setDuration(1000)
|
|
112
|
+
self.fade_anim.setStartValue(0.0)
|
|
113
|
+
self.fade_anim.setEndValue(1.0)
|
|
114
|
+
self.fade_anim.setEasingCurve(QEasingCurve.Type.InOutQuad)
|
|
115
|
+
|
|
116
|
+
# Set up progress animation
|
|
117
|
+
self.progress_anim = QPropertyAnimation(self, b"progress")
|
|
118
|
+
self.progress_anim.setDuration(2000)
|
|
119
|
+
self.progress_anim.setStartValue(0.0)
|
|
120
|
+
self.progress_anim.setEndValue(1.0)
|
|
121
|
+
self.progress_anim.setEasingCurve(QEasingCurve.Type.InOutQuad)
|
|
122
|
+
|
|
123
|
+
# Start animations after everything is initialized
|
|
124
|
+
QTimer.singleShot(100, self.start_animations) # Small delay to ensure everything is ready
|
|
125
|
+
|
|
126
|
+
def start_animations(self):
|
|
127
|
+
"""Start all animations"""
|
|
128
|
+
self.movie.start()
|
|
129
|
+
self.fade_anim.start()
|
|
130
|
+
self.progress_anim.start()
|
|
131
|
+
self.progress_anim.finished.connect(self._on_animation_finished)
|
|
132
|
+
|
|
133
|
+
@pyqtProperty(float)
|
|
134
|
+
def opacity(self):
|
|
135
|
+
return self._opacity
|
|
136
|
+
|
|
137
|
+
@opacity.setter
|
|
138
|
+
def opacity(self, value):
|
|
139
|
+
self._opacity = value
|
|
140
|
+
# Update opacity of overlay and text
|
|
141
|
+
self.overlay.setStyleSheet(f"background-color: rgba(0, 0, 0, {int(100 * value)});")
|
|
142
|
+
self.text_label.setStyleSheet(f"""
|
|
143
|
+
QLabel {{
|
|
144
|
+
color: rgba(255, 255, 255, {int(255 * value)});
|
|
145
|
+
background: transparent;
|
|
146
|
+
text-shadow: 2px 2px 4px rgba(0, 0, 0, {int(180 * value)}),
|
|
147
|
+
0px 0px 10px rgba(52, 152, 219, {int(160 * value)});
|
|
148
|
+
}}
|
|
149
|
+
""")
|
|
150
|
+
|
|
151
|
+
@pyqtProperty(float)
|
|
152
|
+
def progress(self):
|
|
153
|
+
return self._progress
|
|
154
|
+
|
|
155
|
+
@progress.setter
|
|
156
|
+
def progress(self, value):
|
|
157
|
+
self._progress = value
|
|
158
|
+
# Update progress bar width
|
|
159
|
+
self.progress_bar.setFixedWidth(int(200 * value))
|
|
160
|
+
|
|
161
|
+
def _on_animation_finished(self):
|
|
162
|
+
"""Handle animation completion"""
|
|
163
|
+
if self.next_widget:
|
|
164
|
+
QTimer.singleShot(500, self._finish_splash)
|
|
165
|
+
|
|
166
|
+
def _finish_splash(self):
|
|
167
|
+
"""Clean up and show the main window"""
|
|
168
|
+
self.movie.stop()
|
|
169
|
+
self.fade_anim.stop()
|
|
170
|
+
self.progress_anim.stop()
|
|
171
|
+
self.close()
|
|
172
|
+
if self.next_widget:
|
|
173
|
+
self.next_widget.show()
|
|
174
|
+
|
|
175
|
+
def finish(self, widget):
|
|
176
|
+
"""Store the widget to show after animation completes"""
|
|
177
|
+
self.next_widget = widget
|
|
@@ -110,28 +110,9 @@ if __name__ == '__main__':
|
|
|
110
110
|
print(f"Number of products: {len(product_df)}")
|
|
111
111
|
|
|
112
112
|
# Print sample queries
|
|
113
|
-
print("\nSample SQL queries
|
|
113
|
+
print("\nSample SQL queries")
|
|
114
114
|
print("""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
JOIN test_data.customer_data c ON s.CustomerID = c.CustomerID;
|
|
119
|
-
|
|
120
|
-
-- Join sales with product data
|
|
121
|
-
SELECT s.*, p.ProductName, p.Category, p.Brand
|
|
122
|
-
FROM test_data.sample_sales_data s
|
|
123
|
-
JOIN test_data.product_catalog p ON s.ProductID = p.ProductID;
|
|
124
|
-
|
|
125
|
-
-- Three-way join with aggregation
|
|
126
|
-
SELECT
|
|
127
|
-
p.Category,
|
|
128
|
-
c.CustomerType,
|
|
129
|
-
COUNT(*) as NumOrders,
|
|
130
|
-
SUM(s.TotalAmount) as TotalRevenue,
|
|
131
|
-
AVG(s.Quantity) as AvgQuantity
|
|
132
|
-
FROM test_data.sample_sales_data s
|
|
133
|
-
JOIN test_data.customer_data c ON s.CustomerID = c.CustomerID
|
|
134
|
-
JOIN test_data.product_catalog p ON s.ProductID = p.ProductID
|
|
135
|
-
GROUP BY p.Category, c.CustomerType
|
|
136
|
-
ORDER BY p.Category, c.CustomerType;
|
|
115
|
+
select * from product_catalog;
|
|
116
|
+
select * from customer_data;
|
|
117
|
+
select * from sample_sales_data;
|
|
137
118
|
""")
|
|
Binary file
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from PyQt6.QtCore import Qt, QRegularExpression
|
|
2
|
+
from PyQt6.QtGui import QFont, QColor, QSyntaxHighlighter, QTextCharFormat
|
|
3
|
+
|
|
4
|
+
class SQLSyntaxHighlighter(QSyntaxHighlighter):
|
|
5
|
+
def __init__(self, document):
|
|
6
|
+
super().__init__(document)
|
|
7
|
+
self.highlighting_rules = []
|
|
8
|
+
|
|
9
|
+
# SQL Keywords
|
|
10
|
+
keyword_format = QTextCharFormat()
|
|
11
|
+
keyword_format.setForeground(QColor("#0000FF")) # Blue
|
|
12
|
+
keyword_format.setFontWeight(QFont.Weight.Bold)
|
|
13
|
+
keywords = [
|
|
14
|
+
"\\bSELECT\\b", "\\bFROM\\b", "\\bWHERE\\b", "\\bAND\\b", "\\bOR\\b",
|
|
15
|
+
"\\bINNER\\b", "\\bOUTER\\b", "\\bLEFT\\b", "\\bRIGHT\\b", "\\bJOIN\\b",
|
|
16
|
+
"\\bON\\b", "\\bGROUP\\b", "\\bBY\\b", "\\bHAVING\\b", "\\bORDER\\b",
|
|
17
|
+
"\\bLIMIT\\b", "\\bOFFSET\\b", "\\bUNION\\b", "\\bEXCEPT\\b", "\\bINTERSECT\\b",
|
|
18
|
+
"\\bCREATE\\b", "\\bTABLE\\b", "\\bINDEX\\b", "\\bVIEW\\b", "\\bINSERT\\b",
|
|
19
|
+
"\\bINTO\\b", "\\bVALUES\\b", "\\bUPDATE\\b", "\\bSET\\b", "\\bDELETE\\b",
|
|
20
|
+
"\\bTRUNCATE\\b", "\\bALTER\\b", "\\bADD\\b", "\\bDROP\\b", "\\bCOLUMN\\b",
|
|
21
|
+
"\\bCONSTRAINT\\b", "\\bPRIMARY\\b", "\\bKEY\\b", "\\bFOREIGN\\b", "\\bREFERENCES\\b",
|
|
22
|
+
"\\bUNIQUE\\b", "\\bNOT\\b", "\\bNULL\\b", "\\bIS\\b", "\\bDISTINCT\\b",
|
|
23
|
+
"\\bCASE\\b", "\\bWHEN\\b", "\\bTHEN\\b", "\\bELSE\\b", "\\bEND\\b",
|
|
24
|
+
"\\bAS\\b", "\\bWITH\\b", "\\bBETWEEN\\b", "\\bLIKE\\b", "\\bIN\\b",
|
|
25
|
+
"\\bEXISTS\\b", "\\bALL\\b", "\\bANY\\b", "\\bSOME\\b", "\\bDESC\\b", "\\bASC\\b"
|
|
26
|
+
]
|
|
27
|
+
for pattern in keywords:
|
|
28
|
+
regex = QRegularExpression(pattern, QRegularExpression.PatternOption.CaseInsensitiveOption)
|
|
29
|
+
self.highlighting_rules.append((regex, keyword_format))
|
|
30
|
+
|
|
31
|
+
# Functions
|
|
32
|
+
function_format = QTextCharFormat()
|
|
33
|
+
function_format.setForeground(QColor("#AA00AA")) # Purple
|
|
34
|
+
functions = [
|
|
35
|
+
"\\bAVG\\b", "\\bCOUNT\\b", "\\bSUM\\b", "\\bMAX\\b", "\\bMIN\\b",
|
|
36
|
+
"\\bCOALESCE\\b", "\\bNVL\\b", "\\bNULLIF\\b", "\\bCAST\\b", "\\bCONVERT\\b",
|
|
37
|
+
"\\bLOWER\\b", "\\bUPPER\\b", "\\bTRIM\\b", "\\bLTRIM\\b", "\\bRTRIM\\b",
|
|
38
|
+
"\\bLENGTH\\b", "\\bSUBSTRING\\b", "\\bREPLACE\\b", "\\bCONCAT\\b",
|
|
39
|
+
"\\bROUND\\b", "\\bFLOOR\\b", "\\bCEIL\\b", "\\bABS\\b", "\\bMOD\\b",
|
|
40
|
+
"\\bCURRENT_DATE\\b", "\\bCURRENT_TIME\\b", "\\bCURRENT_TIMESTAMP\\b",
|
|
41
|
+
"\\bEXTRACT\\b", "\\bDATE_PART\\b", "\\bTO_CHAR\\b", "\\bTO_DATE\\b"
|
|
42
|
+
]
|
|
43
|
+
for pattern in functions:
|
|
44
|
+
regex = QRegularExpression(pattern, QRegularExpression.PatternOption.CaseInsensitiveOption)
|
|
45
|
+
self.highlighting_rules.append((regex, function_format))
|
|
46
|
+
|
|
47
|
+
# Numbers
|
|
48
|
+
number_format = QTextCharFormat()
|
|
49
|
+
number_format.setForeground(QColor("#009900")) # Green
|
|
50
|
+
self.highlighting_rules.append((
|
|
51
|
+
QRegularExpression("\\b[0-9]+\\b"),
|
|
52
|
+
number_format
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
# Single-line string literals
|
|
56
|
+
string_format = QTextCharFormat()
|
|
57
|
+
string_format.setForeground(QColor("#CC6600")) # Orange/Brown
|
|
58
|
+
self.highlighting_rules.append((
|
|
59
|
+
QRegularExpression("'[^']*'"),
|
|
60
|
+
string_format
|
|
61
|
+
))
|
|
62
|
+
self.highlighting_rules.append((
|
|
63
|
+
QRegularExpression("\"[^\"]*\""),
|
|
64
|
+
string_format
|
|
65
|
+
))
|
|
66
|
+
|
|
67
|
+
# Comments
|
|
68
|
+
comment_format = QTextCharFormat()
|
|
69
|
+
comment_format.setForeground(QColor("#777777")) # Gray
|
|
70
|
+
comment_format.setFontItalic(True)
|
|
71
|
+
self.highlighting_rules.append((
|
|
72
|
+
QRegularExpression("--[^\n]*"),
|
|
73
|
+
comment_format
|
|
74
|
+
))
|
|
75
|
+
|
|
76
|
+
# Multi-line comments
|
|
77
|
+
self.comment_start_expression = QRegularExpression("/\\*")
|
|
78
|
+
self.comment_end_expression = QRegularExpression("\\*/")
|
|
79
|
+
self.multi_line_comment_format = comment_format
|
|
80
|
+
|
|
81
|
+
def highlightBlock(self, text):
|
|
82
|
+
# Apply regular expression highlighting rules
|
|
83
|
+
for pattern, format in self.highlighting_rules:
|
|
84
|
+
match_iterator = pattern.globalMatch(text)
|
|
85
|
+
while match_iterator.hasNext():
|
|
86
|
+
match = match_iterator.next()
|
|
87
|
+
self.setFormat(match.capturedStart(), match.capturedLength(), format)
|
|
88
|
+
|
|
89
|
+
# Handle multi-line comments
|
|
90
|
+
self.setCurrentBlockState(0)
|
|
91
|
+
|
|
92
|
+
# If previous block was inside a comment, check if this block continues it
|
|
93
|
+
start_index = 0
|
|
94
|
+
if self.previousBlockState() != 1:
|
|
95
|
+
# Find the start of a comment
|
|
96
|
+
start_match = self.comment_start_expression.match(text)
|
|
97
|
+
if start_match.hasMatch():
|
|
98
|
+
start_index = start_match.capturedStart()
|
|
99
|
+
else:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
while start_index >= 0:
|
|
103
|
+
# Find the end of the comment
|
|
104
|
+
end_match = self.comment_end_expression.match(text, start_index)
|
|
105
|
+
|
|
106
|
+
# If end match found
|
|
107
|
+
if end_match.hasMatch():
|
|
108
|
+
end_index = end_match.capturedStart()
|
|
109
|
+
comment_length = end_index - start_index + end_match.capturedLength()
|
|
110
|
+
self.setFormat(start_index, comment_length, self.multi_line_comment_format)
|
|
111
|
+
|
|
112
|
+
# Look for next comment
|
|
113
|
+
start_match = self.comment_start_expression.match(text, start_index + comment_length)
|
|
114
|
+
if start_match.hasMatch():
|
|
115
|
+
start_index = start_match.capturedStart()
|
|
116
|
+
else:
|
|
117
|
+
start_index = -1
|
|
118
|
+
else:
|
|
119
|
+
# No end found, comment continues to next block
|
|
120
|
+
self.setCurrentBlockState(1) # Still inside comment
|
|
121
|
+
comment_length = len(text) - start_index
|
|
122
|
+
self.setFormat(start_index, comment_length, self.multi_line_comment_format)
|
|
123
|
+
start_index = -1
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlshell
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: A powerful SQL shell with GUI interface for data analysis
|
|
5
|
+
Home-page: https://github.com/yourusername/sqlshell
|
|
6
|
+
Author: SQLShell Team
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/oyvinrog/SQLShell
|
|
9
|
+
Keywords: sql,data analysis,gui,duckdb
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: pandas>=2.0.0
|
|
20
|
+
Requires-Dist: numpy>=1.24.0
|
|
21
|
+
Requires-Dist: PyQt6>=6.4.0
|
|
22
|
+
Requires-Dist: duckdb>=0.9.0
|
|
23
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
24
|
+
Requires-Dist: pyarrow>=14.0.1
|
|
25
|
+
Requires-Dist: fastparquet>=2023.10.1
|
|
26
|
+
Requires-Dist: xlrd>=2.0.1
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
|
|
30
|
+
# SQLShell
|
|
31
|
+
|
|
32
|
+
<div align="center">
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
**A modern SQL REPL interface for seamless querying of Excel, Parquet, and SQLite databases**
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
## 🚀 Key Features
|
|
43
|
+
|
|
44
|
+
- **Interactive SQL Interface** - Rich syntax highlighting for enhanced query writing
|
|
45
|
+
- **DuckDB Integration** - Built-in support for local DuckDB database (pool.db)
|
|
46
|
+
- **Multi-Format Support** - Import and query Excel (.xlsx, .xls) and CSV files effortlessly
|
|
47
|
+
- **Modern UI** - Clean, tabular results display with intuitive controls
|
|
48
|
+
- **Productivity Tools** - Streamlined workflow with keyboard shortcuts (e.g., Ctrl+Enter for query execution)
|
|
49
|
+
|
|
50
|
+
## 📦 Installation
|
|
51
|
+
|
|
52
|
+
### Linux Setup with Virtual Environment
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Create and activate virtual environment
|
|
56
|
+
python3 -m venv ~/.venv/sqlshell
|
|
57
|
+
source ~/.venv/sqlshell/bin/activate
|
|
58
|
+
|
|
59
|
+
# Install SQLShell
|
|
60
|
+
pip install sqlshell
|
|
61
|
+
|
|
62
|
+
# Configure shell alias
|
|
63
|
+
echo 'alias sqls="~/.venv/sqlshell/bin/sqls"' >> ~/.bashrc # or ~/.zshrc for Zsh
|
|
64
|
+
source ~/.bashrc # or source ~/.zshrc
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Windows Quick Start
|
|
68
|
+
SQLShell is immediately available via the `sqls` command after installation:
|
|
69
|
+
```bash
|
|
70
|
+
pip install sqlshell
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 🎯 Getting Started
|
|
74
|
+
|
|
75
|
+
1. **Launch the Application**
|
|
76
|
+
```bash
|
|
77
|
+
sqls
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. **Database Connection**
|
|
81
|
+
- SQLShell automatically connects to a local DuckDB database named 'pool.db'
|
|
82
|
+
|
|
83
|
+
3. **Working with Excel Files**
|
|
84
|
+
- Click "Browse Excel" to select your file
|
|
85
|
+
- File contents are loaded as 'imported_data' table
|
|
86
|
+
- Query using standard SQL syntax
|
|
87
|
+
|
|
88
|
+
4. **Query Execution**
|
|
89
|
+
- Enter SQL in the editor
|
|
90
|
+
- Execute using Ctrl+Enter or the "Execute" button
|
|
91
|
+
- View results in the structured output panel
|
|
92
|
+
|
|
93
|
+
## 📝 Query Examples
|
|
94
|
+
|
|
95
|
+
### Basic Join Operation
|
|
96
|
+
```sql
|
|
97
|
+
SELECT *
|
|
98
|
+
FROM sample_sales_data cd
|
|
99
|
+
INNER JOIN product_catalog pc ON pc.productid = cd.productid
|
|
100
|
+
LIMIT 3;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Multi-Statement Queries
|
|
104
|
+
```sql
|
|
105
|
+
-- Create a temporary view
|
|
106
|
+
CREATE OR REPLACE TEMPORARY VIEW test_v AS
|
|
107
|
+
SELECT *
|
|
108
|
+
FROM sample_sales_data cd
|
|
109
|
+
INNER JOIN product_catalog pc ON pc.productid = cd.productid;
|
|
110
|
+
|
|
111
|
+
-- Query the view
|
|
112
|
+
SELECT DISTINCT productid
|
|
113
|
+
FROM test_v;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 💡 Pro Tips
|
|
117
|
+
|
|
118
|
+
- Use temporary views for complex query organization
|
|
119
|
+
- Leverage keyboard shortcuts for efficient workflow
|
|
120
|
+
- Explore the multi-format support for various data sources
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
sqlshell/__init__.py,sha256=DqhqqdKdvfwO2iUG1Yd7WPmInpG-OcGhqP8LgblopgI,164
|
|
2
|
+
sqlshell/create_test_data.py,sha256=AzrEsKlZsR1H1Xlp7IKOzWQ2N6FsVISJbc6CerI8IJ0,1954
|
|
3
|
+
sqlshell/editor.py,sha256=4252dxINzJd1jr7CqgSI6IxUYHdkZ1OGZ1qBYN5-MKM,14396
|
|
4
|
+
sqlshell/main.py,sha256=0eQlAQnRkFV14Ot8vJikG7Vq_zkSAqqohu50Y8UREyQ,75480
|
|
5
|
+
sqlshell/setup.py,sha256=bAIXTpgAHhBRmPdT13Klzq16cjd4w4NOYSbyV_rxjlQ,1245
|
|
6
|
+
sqlshell/splash_screen.py,sha256=XkXvSURgQzZfxJvP231Ue4J2E49ph21tWbvfGU03Ogk,6914
|
|
7
|
+
sqlshell/sqlshell_demo.png,sha256=dPp9J1FVqQVfrh-gekosuha2Jw2p2--wxbOmt2kr7fg,133550
|
|
8
|
+
sqlshell/syntax_highlighter.py,sha256=mPwsD8N4XzAUx0IgwlelyfjUhe0xmH0Ug3UI9hTcHz0,5861
|
|
9
|
+
sqlshell/data/create_test_data.py,sha256=sUTcf50V8-bVwYV2VNTLK65c-iHiU4wb99By67I10zM,5404
|
|
10
|
+
sqlshell/resources/__init__.py,sha256=VLTJ_5pUHhctRiV8UZDvG-jnsjgT6JQvW-ZPzIJqBIY,44
|
|
11
|
+
sqlshell/resources/create_icon.py,sha256=ubNYyBYx5HtYpV8eiGY8Fb1VWUoQt-npaqUckiCimnQ,2030
|
|
12
|
+
sqlshell/resources/create_splash.py,sha256=cj6BpMtD1crBkcSxt-ssJIFIZv43UDqLME8FF0fgkoA,2104
|
|
13
|
+
sqlshell/resources/splash_screen.gif,sha256=H24DQBdK1EsqQTWZkgInjM5ouOzY4cMesUoza10auNg,3070484
|
|
14
|
+
sqlshell/sqlshell/__init__.py,sha256=6Wp5nabfTzH5rkC-2jYo_ZjCuw8utmj21Jpy8vBuliI,100
|
|
15
|
+
sqlshell/sqlshell/create_test_data.py,sha256=TFXgWeK1l3l_c4Dg38yS8Df4sBUfOZcBucXngtpThvk,4624
|
|
16
|
+
sqlshell/sqlshell/create_test_databases.py,sha256=oqryFJJahqLFsAjBFM4r9Fe1ea7djDcRpT9U_aBf7PU,3573
|
|
17
|
+
sqlshell-0.1.8.dist-info/METADATA,sha256=bIt-ocCENwgCT7ZxBMOUOaABM0PWonDQ7x2rYy2j2fU,3323
|
|
18
|
+
sqlshell-0.1.8.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
19
|
+
sqlshell-0.1.8.dist-info/entry_points.txt,sha256=Kd0fOvyOW7UiTgTVY7abVOmDIH2Y2nawGTp5kVadac4,44
|
|
20
|
+
sqlshell-0.1.8.dist-info/top_level.txt,sha256=ahwsMFhvAqI97ZkT2xvHL5iZCO1p13mNiUOFkdSFwms,9
|
|
21
|
+
sqlshell-0.1.8.dist-info/RECORD,,
|