sqlshell 0.4.4__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.
Files changed (54) hide show
  1. sqlshell/__init__.py +84 -0
  2. sqlshell/__main__.py +4926 -0
  3. sqlshell/ai_autocomplete.py +392 -0
  4. sqlshell/ai_settings_dialog.py +337 -0
  5. sqlshell/context_suggester.py +768 -0
  6. sqlshell/create_test_data.py +152 -0
  7. sqlshell/data/create_test_data.py +137 -0
  8. sqlshell/db/__init__.py +6 -0
  9. sqlshell/db/database_manager.py +1318 -0
  10. sqlshell/db/export_manager.py +188 -0
  11. sqlshell/editor.py +1166 -0
  12. sqlshell/editor_integration.py +127 -0
  13. sqlshell/execution_handler.py +421 -0
  14. sqlshell/menus.py +262 -0
  15. sqlshell/notification_manager.py +370 -0
  16. sqlshell/query_tab.py +904 -0
  17. sqlshell/resources/__init__.py +1 -0
  18. sqlshell/resources/icon.png +0 -0
  19. sqlshell/resources/logo_large.png +0 -0
  20. sqlshell/resources/logo_medium.png +0 -0
  21. sqlshell/resources/logo_small.png +0 -0
  22. sqlshell/resources/splash_screen.gif +0 -0
  23. sqlshell/space_invaders.py +501 -0
  24. sqlshell/splash_screen.py +405 -0
  25. sqlshell/sqlshell/__init__.py +5 -0
  26. sqlshell/sqlshell/create_test_data.py +118 -0
  27. sqlshell/sqlshell/create_test_databases.py +96 -0
  28. sqlshell/sqlshell_demo.png +0 -0
  29. sqlshell/styles.py +257 -0
  30. sqlshell/suggester_integration.py +330 -0
  31. sqlshell/syntax_highlighter.py +124 -0
  32. sqlshell/table_list.py +996 -0
  33. sqlshell/ui/__init__.py +6 -0
  34. sqlshell/ui/bar_chart_delegate.py +49 -0
  35. sqlshell/ui/filter_header.py +469 -0
  36. sqlshell/utils/__init__.py +16 -0
  37. sqlshell/utils/profile_cn2.py +1661 -0
  38. sqlshell/utils/profile_column.py +2635 -0
  39. sqlshell/utils/profile_distributions.py +616 -0
  40. sqlshell/utils/profile_entropy.py +347 -0
  41. sqlshell/utils/profile_foreign_keys.py +779 -0
  42. sqlshell/utils/profile_keys.py +2834 -0
  43. sqlshell/utils/profile_ohe.py +934 -0
  44. sqlshell/utils/profile_ohe_advanced.py +754 -0
  45. sqlshell/utils/profile_ohe_comparison.py +237 -0
  46. sqlshell/utils/profile_prediction.py +926 -0
  47. sqlshell/utils/profile_similarity.py +876 -0
  48. sqlshell/utils/search_in_df.py +90 -0
  49. sqlshell/widgets.py +400 -0
  50. sqlshell-0.4.4.dist-info/METADATA +441 -0
  51. sqlshell-0.4.4.dist-info/RECORD +54 -0
  52. sqlshell-0.4.4.dist-info/WHEEL +5 -0
  53. sqlshell-0.4.4.dist-info/entry_points.txt +2 -0
  54. sqlshell-0.4.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,405 @@
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, QPixmap
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
+ self.use_fallback = False
15
+
16
+ # Set up the window properties
17
+ self.setWindowFlags(
18
+ Qt.WindowType.WindowStaysOnTopHint |
19
+ Qt.WindowType.FramelessWindowHint |
20
+ Qt.WindowType.SplashScreen
21
+ )
22
+
23
+ # Set widget attributes for proper compositing
24
+ self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
25
+ self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
26
+ self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, False)
27
+
28
+ # Set fixed size
29
+ self.setFixedSize(400, 300)
30
+
31
+ # Center the splash screen on the screen
32
+ screen_geometry = self.screen().geometry()
33
+ self.move(
34
+ (screen_geometry.width() - self.width()) // 2,
35
+ (screen_geometry.height() - self.height()) // 2
36
+ )
37
+
38
+ # Create movie label first (background)
39
+ self.movie_label = QLabel(self)
40
+ self.movie_label.setGeometry(0, 0, self.width(), self.height())
41
+
42
+ # Create overlay for fade effect (between movie and content)
43
+ self.overlay = QLabel(self)
44
+ self.overlay.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
45
+ self.overlay.setGeometry(0, 0, self.width(), self.height())
46
+
47
+ # Create text label for animated text
48
+ self.text_label = QLabel(self)
49
+ self.text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
50
+ self.text_label.setStyleSheet("color: rgba(255, 255, 255, 0); background: transparent;")
51
+ self.text_label.setGeometry(0, 0, self.width(), self.height())
52
+
53
+ # Create layout
54
+ layout = QVBoxLayout(self)
55
+ layout.setContentsMargins(20, 140, 20, 20) # Increased top margin to accommodate title bar and logo
56
+ layout.setSpacing(10)
57
+
58
+ # Create background container for the subtitle
59
+ self.content_container = QWidget(self)
60
+ self.content_container.setStyleSheet("background: transparent;")
61
+ content_layout = QVBoxLayout(self.content_container)
62
+ content_layout.setContentsMargins(20, 5, 20, 20)
63
+ content_layout.setSpacing(5)
64
+
65
+ # Create subtitle label
66
+ self.subtitle_label = QLabel("Loading...", self.content_container)
67
+ self.subtitle_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
68
+ self.subtitle_label.setStyleSheet("""
69
+ QLabel {
70
+ color: #2C3E50;
71
+ font-size: 16px;
72
+ font-family: 'Segoe UI', Arial, sans-serif;
73
+ background: transparent;
74
+ }
75
+ """)
76
+ content_layout.addWidget(self.subtitle_label)
77
+
78
+ # Add content container to main layout
79
+ layout.addWidget(self.content_container)
80
+
81
+ # Create progress bar (always on top)
82
+ self.progress_bar = QLabel(self)
83
+ self.progress_bar.setFixedHeight(4)
84
+ self.progress_bar.setStyleSheet("background-color: #3498DB; border-radius: 2px;")
85
+ self.progress_bar.move(100, self.height() - 40)
86
+ self.progress_bar.setFixedWidth(0)
87
+
88
+ # Create a top overlay widget that will always be on top
89
+ self.top_overlay = QWidget(self)
90
+ self.top_overlay.setGeometry(0, 0, self.width(), 130) # Covers title and logo area
91
+ self.top_overlay.setStyleSheet("background: transparent;")
92
+
93
+ # Now create the top title and logo elements on the overlay
94
+
95
+ # Create title bar at the very top
96
+ self.title_bar = QLabel(self.top_overlay)
97
+ self.title_bar.setText("SQL Shell") # Set the text
98
+ self.title_bar.setFixedSize(self.width(), 50)
99
+ self.title_bar.move(0, 0)
100
+ self.title_bar.setAlignment(Qt.AlignmentFlag.AlignCenter)
101
+ self.title_bar.setStyleSheet("""
102
+ QLabel {
103
+ color: white;
104
+ font-size: 28px;
105
+ font-weight: bold;
106
+ font-family: 'Segoe UI', Arial, sans-serif;
107
+ background-color: rgba(52, 152, 219, 0.9);
108
+ border-bottom: 2px solid #2980B9;
109
+ border-top-left-radius: 10px;
110
+ border-top-right-radius: 10px;
111
+ }
112
+ """)
113
+
114
+ # Create a dedicated logo container right below the title bar
115
+ self.logo_container = QLabel(self.top_overlay)
116
+ self.logo_container.setFixedSize(self.width(), 80)
117
+ self.logo_container.move(0, 50) # Position right below title bar
118
+ self.logo_container.setAlignment(Qt.AlignmentFlag.AlignCenter)
119
+ self.logo_container.setStyleSheet("background: rgba(255, 255, 255, 0.8); border: 0px;")
120
+
121
+ # Try to load logo directly here
122
+ logo_path = os.path.join(os.path.dirname(__file__), "resources", "logo_medium.png")
123
+ if os.path.exists(logo_path):
124
+ logo_pixmap = QPixmap(logo_path)
125
+ # Scale logo to appropriate size
126
+ scaled_logo = logo_pixmap.scaledToWidth(200, Qt.TransformationMode.SmoothTransformation)
127
+ self.logo_container.setPixmap(scaled_logo)
128
+ print(f"Logo loaded with size: {scaled_logo.width()}x{scaled_logo.height()}")
129
+ print(f"Logo container geometry: {self.logo_container.geometry()}")
130
+ else:
131
+ print(f"Logo not found at path: {logo_path}")
132
+ # Try the small logo as fallback
133
+ logo_path = os.path.join(os.path.dirname(__file__), "resources", "logo_small.png")
134
+ if os.path.exists(logo_path):
135
+ logo_pixmap = QPixmap(logo_path)
136
+ scaled_logo = logo_pixmap.scaledToWidth(150, Qt.TransformationMode.SmoothTransformation)
137
+ self.logo_container.setPixmap(scaled_logo)
138
+ print(f"Fallback logo loaded with size: {scaled_logo.width()}x{scaled_logo.height()}")
139
+ print(f"Logo container geometry: {self.logo_container.geometry()}")
140
+
141
+ print(f"Title bar geometry: {self.title_bar.geometry()}")
142
+ print(f"Title bar text: {self.title_bar.text()}")
143
+ print(f"Top overlay geometry: {self.top_overlay.geometry()}")
144
+
145
+ # Set appropriate z-order of elements
146
+ self.movie_label.lower() # Background at the very back
147
+ self.overlay.raise_() # Overlay above background
148
+ self.text_label.raise_() # Text above overlay
149
+ self.content_container.raise_() # Content above text
150
+ self.progress_bar.raise_() # Progress bar on top
151
+ self.top_overlay.raise_() # Top overlay with title and logo at the very top
152
+
153
+ # Set up the loading animation - do it immediately in init
154
+ self.movie = None # Initialize to None for safety
155
+ self.load_animation()
156
+
157
+ # Set up fade animation
158
+ self.fade_anim = QPropertyAnimation(self, b"opacity")
159
+ self.fade_anim.setDuration(1000)
160
+ self.fade_anim.setStartValue(0.0)
161
+ self.fade_anim.setEndValue(1.0)
162
+ self.fade_anim.setEasingCurve(QEasingCurve.Type.InOutQuad)
163
+
164
+ # Set up progress animation
165
+ self.progress_anim = QPropertyAnimation(self, b"progress")
166
+ self.progress_anim.setDuration(2000)
167
+ self.progress_anim.setStartValue(0.0)
168
+ self.progress_anim.setEndValue(1.0)
169
+ self.progress_anim.setEasingCurve(QEasingCurve.Type.InOutQuad)
170
+
171
+ # Create a dedicated timer to ensure title and logo always stay on top
172
+ self.z_order_timer = QTimer(self)
173
+ self.z_order_timer.timeout.connect(self.ensure_top_elements_visible)
174
+ self.z_order_timer.start(50) # Check every 50ms
175
+
176
+ # Start animations after everything is initialized
177
+ QTimer.singleShot(100, self.start_animations) # Small delay to ensure everything is ready
178
+
179
+ def load_animation(self):
180
+ """Load the splash screen animation"""
181
+ # Check multiple potential paths for the splash screen GIF
182
+ possible_paths = [
183
+ os.path.join(os.path.dirname(__file__), "resources", "splash_screen.gif"),
184
+ os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "splash_screen.gif"),
185
+ os.path.join(os.path.dirname(__file__), "splash_screen.gif"),
186
+ os.path.abspath("sqlshell/resources/splash_screen.gif"),
187
+ os.path.abspath("resources/splash_screen.gif")
188
+ ]
189
+
190
+ # Try each possible path
191
+ for path in possible_paths:
192
+ if os.path.exists(path):
193
+ print(f"Loading splash screen animation from: {path}")
194
+ try:
195
+ self.movie = QMovie(path)
196
+ self.movie.setCacheMode(QMovie.CacheMode.CacheAll) # Cache all frames for smoother playback
197
+ self.movie.setScaledSize(self.size())
198
+
199
+ # Connect frameChanged signal to update the label
200
+ self.movie.frameChanged.connect(self.update_frame)
201
+
202
+ # Ensure the movie label is visible but below other elements
203
+ self.movie_label.lower()
204
+ self.movie_label.setStyleSheet("background: transparent;")
205
+
206
+ # Set the movie to the label
207
+ self.movie_label.setMovie(self.movie)
208
+
209
+ # Test if the movie is valid
210
+ if self.movie.isValid():
211
+ print(f"Successfully loaded animation with {self.movie.frameCount()} frames")
212
+ self.use_fallback = False
213
+
214
+ # Create a timer to ensure animation updates
215
+ self.animation_timer = QTimer(self)
216
+ self.animation_timer.timeout.connect(self.update_animation)
217
+ self.animation_timer.start(50) # Update every 50ms
218
+
219
+ # Force our top overlay to be visible
220
+ self.top_overlay.raise_()
221
+
222
+ return
223
+ else:
224
+ print(f"Warning: Animation file at {path} is not valid")
225
+ self.use_fallback = True
226
+ except Exception as e:
227
+ print(f"Error loading animation from {path}: {e}")
228
+
229
+ # If we get here, no valid animation was found
230
+ print("No valid animation found, using fallback static splash screen")
231
+ self.use_fallback = True
232
+
233
+ def update_frame(self):
234
+ """Handle frame changed in the animation"""
235
+ # Make sure the movie label is refreshed and visible
236
+ self.movie_label.update()
237
+ self.movie_label.show()
238
+
239
+ # Always ensure title and logo stay on top
240
+ self.title_bar.raise_()
241
+ self.logo_container.raise_()
242
+
243
+ def update_animation(self):
244
+ """Ensure animation keeps running"""
245
+ if self.movie and not self.use_fallback:
246
+ # Check if movie is running
247
+ if self.movie.state() != QMovie.MovieState.Running:
248
+ self.movie.start()
249
+
250
+ # Force update of the movie label
251
+ self.movie_label.update()
252
+
253
+ # Always ensure title and logo stay on top
254
+ self.title_bar.raise_()
255
+ self.logo_container.raise_()
256
+
257
+ def paintEvent(self, event):
258
+ """Custom paint event to draw a fallback splash screen if needed"""
259
+ if self.use_fallback:
260
+ painter = QPainter(self)
261
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
262
+
263
+ # Draw rounded rectangle background
264
+ gradient = QLinearGradient(0, 0, 0, self.height())
265
+ gradient.setColorAt(0, QColor(44, 62, 80)) # Dark blue-gray
266
+ gradient.setColorAt(1, QColor(52, 152, 219)) # Bright blue
267
+
268
+ painter.setBrush(gradient)
269
+ painter.setPen(Qt.PenStyle.NoPen)
270
+ painter.drawRoundedRect(0, 0, self.width(), self.height(), 10, 10)
271
+
272
+ # Draw title bar at the top
273
+ title_rect = QRect(0, 0, self.width(), 50)
274
+ painter.setBrush(QColor(52, 152, 219)) # Bright blue
275
+ painter.drawRect(title_rect)
276
+
277
+ # Draw title text
278
+ painter.setPen(QColor(255, 255, 255))
279
+ font = QFont("Segoe UI", 24)
280
+ font.setBold(True)
281
+ painter.setFont(font)
282
+ painter.drawText(title_rect, Qt.AlignmentFlag.AlignCenter, "SQL Shell")
283
+
284
+ # Try to draw logo
285
+ logo_path = os.path.join(os.path.dirname(__file__), "resources", "logo_small.png")
286
+ if os.path.exists(logo_path):
287
+ logo_pixmap = QPixmap(logo_path)
288
+ scaled_logo = logo_pixmap.scaledToHeight(70, Qt.TransformationMode.SmoothTransformation)
289
+ logo_x = (self.width() - scaled_logo.width()) // 2
290
+ painter.drawPixmap(logo_x, 60, scaled_logo)
291
+
292
+ # Draw progress bar
293
+ if hasattr(self, '_progress'):
294
+ progress_width = int(200 * self._progress)
295
+ progress_rect = QRect(100, self.height() - 40, progress_width, 4)
296
+ painter.setBrush(QColor(26, 188, 156)) # Teal
297
+ painter.drawRoundedRect(progress_rect, 2, 2)
298
+
299
+ super().paintEvent(event)
300
+
301
+ def start_animations(self):
302
+ """Start all animations"""
303
+ # Try to start the movie if we have one
304
+ if self.movie and not self.use_fallback:
305
+ self.movie.start()
306
+
307
+ # Check if movie is running after attempt to start
308
+ if not self.movie.state() == QMovie.MovieState.Running:
309
+ print("Warning: Could not start the animation")
310
+ self.use_fallback = True
311
+ else:
312
+ # Ensure the movie label is visible and updated
313
+ self.movie_label.show()
314
+ self.movie_label.update()
315
+
316
+ self.fade_anim.start()
317
+ self.progress_anim.start()
318
+ self.progress_anim.finished.connect(self._on_animation_finished)
319
+
320
+ @pyqtProperty(float)
321
+ def opacity(self):
322
+ return self._opacity
323
+
324
+ @opacity.setter
325
+ def opacity(self, value):
326
+ self._opacity = value
327
+ # Update opacity of overlay and text
328
+ self.overlay.setStyleSheet(f"background-color: rgba(0, 0, 0, {int(100 * value)});")
329
+ self.text_label.setStyleSheet(f"""
330
+ QLabel {{
331
+ color: rgba(255, 255, 255, {int(255 * value)});
332
+ background: transparent;
333
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, {int(180 * value)}),
334
+ 0px 0px 10px rgba(52, 152, 219, {int(160 * value)});
335
+ }}
336
+ """)
337
+
338
+ @pyqtProperty(float)
339
+ def progress(self):
340
+ return self._progress
341
+
342
+ @progress.setter
343
+ def progress(self, value):
344
+ self._progress = value
345
+ # Update progress bar width
346
+ if hasattr(self, 'progress_bar'):
347
+ self.progress_bar.setFixedWidth(int(200 * value))
348
+ # Force repaint if using fallback
349
+ if self.use_fallback:
350
+ self.update()
351
+
352
+ def ensure_top_elements_visible(self):
353
+ """Ensure title bar and logo container are always on top"""
354
+ self.top_overlay.raise_()
355
+
356
+ def _on_animation_finished(self):
357
+ """Handle animation completion"""
358
+ if self.next_widget:
359
+ QTimer.singleShot(500, self._finish_splash)
360
+
361
+ def _finish_splash(self):
362
+ """Clean up and show the main window"""
363
+ # Stop the animation timer if it exists
364
+ if hasattr(self, 'animation_timer') and self.animation_timer:
365
+ self.animation_timer.stop()
366
+
367
+ # Stop the z-order timer
368
+ if hasattr(self, 'z_order_timer'):
369
+ self.z_order_timer.stop()
370
+
371
+ if self.movie:
372
+ self.movie.stop()
373
+ if self.fade_anim:
374
+ self.fade_anim.stop()
375
+ if self.progress_anim:
376
+ self.progress_anim.stop()
377
+ self.close()
378
+ if self.next_widget:
379
+ self.next_widget.show()
380
+
381
+ def finish(self, widget):
382
+ """Store the widget to show after animation completes"""
383
+ self.next_widget = widget
384
+
385
+ # On Windows, we need to explicitly trigger the finish process
386
+ # instead of waiting for the animation to complete
387
+
388
+ # First forcibly stop all animations
389
+ if hasattr(self, 'animation_timer') and self.animation_timer:
390
+ self.animation_timer.stop()
391
+
392
+ # Stop the z-order timer
393
+ if hasattr(self, 'z_order_timer'):
394
+ self.z_order_timer.stop()
395
+
396
+ if self.movie:
397
+ self.movie.stop()
398
+ if self.fade_anim:
399
+ self.fade_anim.stop()
400
+ if self.progress_anim:
401
+ self.progress_anim.stop()
402
+
403
+ # Close the splash screen and show the main window directly
404
+ # Use a very short timer to allow the event loop to process
405
+ QTimer.singleShot(50, self._finish_splash)
@@ -0,0 +1,5 @@
1
+ """
2
+ SQLShell - A powerful SQL shell with GUI interface for data analysis
3
+ """
4
+
5
+ __version__ = "0.1.1"
@@ -0,0 +1,118 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from datetime import datetime, timedelta
4
+ import os
5
+
6
+ # Set random seed for reproducibility
7
+ np.random.seed(42)
8
+
9
+ # Define output directory
10
+ OUTPUT_DIR = 'test_data'
11
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
12
+
13
+ def create_sales_data(num_records=1000):
14
+ # Generate dates for the last 365 days
15
+ end_date = datetime.now()
16
+ start_date = end_date - timedelta(days=365)
17
+ dates = [start_date + timedelta(days=x) for x in range(366)]
18
+ random_dates = np.random.choice(dates, num_records)
19
+
20
+ # Create product data
21
+ products = ['Laptop', 'Smartphone', 'Tablet', 'Monitor', 'Keyboard', 'Mouse', 'Headphones', 'Printer']
22
+ product_prices = {
23
+ 'Laptop': (800, 2000),
24
+ 'Smartphone': (400, 1200),
25
+ 'Tablet': (200, 800),
26
+ 'Monitor': (150, 500),
27
+ 'Keyboard': (20, 150),
28
+ 'Mouse': (10, 80),
29
+ 'Headphones': (30, 300),
30
+ 'Printer': (100, 400)
31
+ }
32
+
33
+ # Generate random data
34
+ data = {
35
+ 'OrderID': range(1, num_records + 1),
36
+ 'Date': random_dates,
37
+ 'ProductID': np.random.randint(1, len(products) + 1, num_records), # Changed to ProductID for joining
38
+ 'Quantity': np.random.randint(1, 11, num_records),
39
+ 'CustomerID': np.random.randint(1, 201, num_records),
40
+ 'Region': np.random.choice(['North', 'South', 'East', 'West'], num_records)
41
+ }
42
+
43
+ # Calculate prices based on product
44
+ product_list = [products[pid-1] for pid in data['ProductID']]
45
+ data['Price'] = [np.random.uniform(product_prices[p][0], product_prices[p][1])
46
+ for p in product_list]
47
+ data['TotalAmount'] = [price * qty for price, qty in zip(data['Price'], data['Quantity'])]
48
+
49
+ # Create DataFrame
50
+ df = pd.DataFrame(data)
51
+
52
+ # Round numerical columns
53
+ df['Price'] = df['Price'].round(2)
54
+ df['TotalAmount'] = df['TotalAmount'].round(2)
55
+
56
+ # Sort by Date
57
+ return df.sort_values('Date')
58
+
59
+ def create_customer_data(num_customers=200):
60
+ # Generate customer data
61
+ data = {
62
+ 'CustomerID': range(1, num_customers + 1),
63
+ 'FirstName': [f'Customer{i}' for i in range(1, num_customers + 1)],
64
+ 'LastName': [f'Lastname{i}' for i in range(1, num_customers + 1)],
65
+ 'Email': [f'customer{i}@example.com' for i in range(1, num_customers + 1)],
66
+ 'JoinDate': [datetime.now() - timedelta(days=np.random.randint(1, 1000))
67
+ for _ in range(num_customers)],
68
+ 'CustomerType': np.random.choice(['Regular', 'Premium', 'VIP'], num_customers),
69
+ 'CreditScore': np.random.randint(300, 851, num_customers)
70
+ }
71
+
72
+ return pd.DataFrame(data)
73
+
74
+ def create_product_data():
75
+ # Create detailed product information
76
+ products = {
77
+ 'ProductID': range(1, 9),
78
+ 'ProductName': ['Laptop', 'Smartphone', 'Tablet', 'Monitor', 'Keyboard', 'Mouse', 'Headphones', 'Printer'],
79
+ 'Category': ['Computers', 'Mobile', 'Mobile', 'Accessories', 'Accessories', 'Accessories', 'Audio', 'Peripherals'],
80
+ 'Brand': ['TechPro', 'MobileX', 'TabletCo', 'ViewMax', 'TypeMaster', 'ClickPro', 'SoundMax', 'PrintPro'],
81
+ 'StockQuantity': np.random.randint(50, 500, 8),
82
+ 'MinPrice': [800, 400, 200, 150, 20, 10, 30, 100],
83
+ 'MaxPrice': [2000, 1200, 800, 500, 150, 80, 300, 400],
84
+ 'Weight_kg': [2.5, 0.2, 0.5, 3.0, 0.8, 0.1, 0.3, 5.0],
85
+ 'WarrantyMonths': [24, 12, 12, 36, 12, 12, 24, 12]
86
+ }
87
+
88
+ return pd.DataFrame(products)
89
+
90
+ if __name__ == '__main__':
91
+ # Create and save sales data
92
+ sales_df = create_sales_data()
93
+ sales_output = os.path.join(OUTPUT_DIR, 'sample_sales_data.xlsx')
94
+ sales_df.to_excel(sales_output, index=False)
95
+ print(f"Created sales data in '{sales_output}'")
96
+ print(f"Number of sales records: {len(sales_df)}")
97
+
98
+ # Create and save customer data as parquet
99
+ customer_df = create_customer_data()
100
+ customer_output = os.path.join(OUTPUT_DIR, 'customer_data.parquet')
101
+ customer_df.to_parquet(customer_output, index=False)
102
+ print(f"\nCreated customer data in '{customer_output}'")
103
+ print(f"Number of customers: {len(customer_df)}")
104
+
105
+ # Create and save product data
106
+ product_df = create_product_data()
107
+ product_output = os.path.join(OUTPUT_DIR, 'product_catalog.xlsx')
108
+ product_df.to_excel(product_output, index=False)
109
+ print(f"\nCreated product catalog in '{product_output}'")
110
+ print(f"Number of products: {len(product_df)}")
111
+
112
+ # Print sample queries
113
+ print("\nSample SQL queries")
114
+ print("""
115
+ select * from product_catalog;
116
+ select * from customer_data;
117
+ select * from sample_sales_data;
118
+ """)
@@ -0,0 +1,96 @@
1
+ import pandas as pd
2
+ import sqlite3
3
+ import duckdb
4
+ import os
5
+
6
+ # Define paths
7
+ TEST_DATA_DIR = 'test_data'
8
+ SQLITE_DB_PATH = os.path.join(TEST_DATA_DIR, 'test.db')
9
+ DUCKDB_PATH = os.path.join(TEST_DATA_DIR, 'test.duckdb')
10
+
11
+ def load_source_data():
12
+ """Load the source Excel and Parquet files."""
13
+ sales_df = pd.read_excel(os.path.join(TEST_DATA_DIR, 'sample_sales_data.xlsx'))
14
+ customer_df = pd.read_parquet(os.path.join(TEST_DATA_DIR, 'customer_data.parquet'))
15
+ product_df = pd.read_excel(os.path.join(TEST_DATA_DIR, 'product_catalog.xlsx'))
16
+ return sales_df, customer_df, product_df
17
+
18
+ def create_sqlite_database():
19
+ """Create SQLite database with the test data."""
20
+ # Remove existing database if it exists
21
+ if os.path.exists(SQLITE_DB_PATH):
22
+ os.remove(SQLITE_DB_PATH)
23
+
24
+ # Load data
25
+ sales_df, customer_df, product_df = load_source_data()
26
+
27
+ # Create connection and write tables
28
+ with sqlite3.connect(SQLITE_DB_PATH) as conn:
29
+ sales_df.to_sql('sales', conn, index=False)
30
+ customer_df.to_sql('customers', conn, index=False)
31
+ product_df.to_sql('products', conn, index=False)
32
+
33
+ # Create indexes for better performance
34
+ conn.execute('CREATE INDEX idx_sales_customer ON sales(CustomerID)')
35
+ conn.execute('CREATE INDEX idx_sales_product ON sales(ProductID)')
36
+
37
+ print(f"Created SQLite database at {SQLITE_DB_PATH}")
38
+
39
+ def create_duckdb_database():
40
+ """Create DuckDB database with the test data."""
41
+ # Remove existing database if it exists
42
+ if os.path.exists(DUCKDB_PATH):
43
+ os.remove(DUCKDB_PATH)
44
+
45
+ # Load data
46
+ sales_df, customer_df, product_df = load_source_data()
47
+
48
+ # Create connection and write tables
49
+ with duckdb.connect(DUCKDB_PATH) as conn:
50
+ conn.execute("CREATE TABLE sales AS SELECT * FROM sales_df")
51
+ conn.execute("CREATE TABLE customers AS SELECT * FROM customer_df")
52
+ conn.execute("CREATE TABLE products AS SELECT * FROM product_df")
53
+
54
+ # Create indexes for better performance
55
+ conn.execute('CREATE INDEX idx_sales_customer ON sales(CustomerID)')
56
+ conn.execute('CREATE INDEX idx_sales_product ON sales(ProductID)')
57
+
58
+ print(f"Created DuckDB database at {DUCKDB_PATH}")
59
+
60
+ def verify_databases():
61
+ """Verify the databases were created correctly by running test queries."""
62
+ # Test SQLite
63
+ with sqlite3.connect(SQLITE_DB_PATH) as conn:
64
+ sales_count = pd.read_sql("SELECT COUNT(*) as count FROM sales", conn).iloc[0]['count']
65
+ print(f"\nSQLite verification:")
66
+ print(f"Sales records: {sales_count}")
67
+
68
+ # Test a join query
69
+ sample_query = """
70
+ SELECT
71
+ p.Category,
72
+ COUNT(*) as NumOrders,
73
+ ROUND(SUM(s.TotalAmount), 2) as TotalRevenue
74
+ FROM sales s
75
+ JOIN products p ON s.ProductID = p.ProductID
76
+ GROUP BY p.Category
77
+ LIMIT 3
78
+ """
79
+ print("\nSample SQLite query result:")
80
+ print(pd.read_sql(sample_query, conn))
81
+
82
+ # Test DuckDB
83
+ with duckdb.connect(DUCKDB_PATH) as conn:
84
+ sales_count = conn.execute("SELECT COUNT(*) as count FROM sales").fetchone()[0]
85
+ print(f"\nDuckDB verification:")
86
+ print(f"Sales records: {sales_count}")
87
+
88
+ # Test the same join query
89
+ print("\nSample DuckDB query result:")
90
+ print(conn.execute(sample_query).df())
91
+
92
+ if __name__ == '__main__':
93
+ print("Creating test databases...")
94
+ create_sqlite_database()
95
+ create_duckdb_database()
96
+ verify_databases()
Binary file