Skip to content

Commit 9434b24

Browse files
authored
Merge pull request #198 from dbcli/ai
Ai
2 parents e239fb7 + f901c7e commit 9434b24

File tree

14 files changed

+625
-51
lines changed

14 files changed

+625
-51
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212

1313
strategy:
1414
matrix:
15-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
15+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1616

1717
steps:
1818
- uses: actions/checkout@v3

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313

1414
strategy:
1515
matrix:
16-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
16+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1717

1818
steps:
1919
- uses: actions/checkout@v4

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## 1.14.0 - 2025-01-20
2+
3+
### Features
4+
5+
* Add LLM feature to ask an LLM to create a SQL query.
6+
- This adds a new `\llm` special command
7+
- eg: `\llm "Who is the largest customer based on revenue?"`
8+
9+
### Internal
10+
11+
* Change min required python version to 3.9+
12+
113
## 1.13.2 - 2024-11-24
214

315
### Internal

litecli/main.py

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,47 @@ def get_continuation(width, line_number, is_soft_wrap):
385385
def show_suggestion_tip():
386386
return iterations < 2
387387

388+
def output_res(res, start):
389+
result_count = 0
390+
mutating = False
391+
for title, cur, headers, status in res:
392+
logger.debug("headers: %r", headers)
393+
logger.debug("rows: %r", cur)
394+
logger.debug("status: %r", status)
395+
threshold = 1000
396+
if is_select(status) and cur and cur.rowcount > threshold:
397+
self.echo(
398+
"The result set has more than {} rows.".format(threshold),
399+
fg="red",
400+
)
401+
if not confirm("Do you want to continue?"):
402+
self.echo("Aborted!", err=True, fg="red")
403+
break
404+
405+
if self.auto_vertical_output:
406+
max_width = self.prompt_app.output.get_size().columns
407+
else:
408+
max_width = None
409+
410+
formatted = self.format_output(title, cur, headers, special.is_expanded_output(), max_width)
411+
412+
t = time() - start
413+
try:
414+
if result_count > 0:
415+
self.echo("")
416+
try:
417+
self.output(formatted, status)
418+
except KeyboardInterrupt:
419+
pass
420+
self.echo("Time: %0.03fs" % t)
421+
except KeyboardInterrupt:
422+
pass
423+
424+
start = time()
425+
result_count += 1
426+
mutating = mutating or is_mutating(status)
427+
return mutating
428+
388429
def one_iteration(text=None):
389430
if text is None:
390431
try:
@@ -402,6 +443,24 @@ def one_iteration(text=None):
402443
self.echo(str(e), err=True, fg="red")
403444
return
404445

446+
if special.is_llm_command(text):
447+
try:
448+
start = time()
449+
cur = self.sqlexecute.conn.cursor()
450+
context, sql = special.handle_llm(text, cur)
451+
if context:
452+
click.echo(context)
453+
text = self.prompt_app.prompt(default=sql)
454+
except KeyboardInterrupt:
455+
return
456+
except special.FinishIteration as e:
457+
return output_res(e.results, start) if e.results else None
458+
except RuntimeError as e:
459+
logger.error("sql: %r, error: %r", text, e)
460+
logger.error("traceback: %r", traceback.format_exc())
461+
self.echo(str(e), err=True, fg="red")
462+
return
463+
405464
if not text.strip():
406465
return
407466

@@ -415,9 +474,6 @@ def one_iteration(text=None):
415474
self.echo("Wise choice!")
416475
return
417476

418-
# Keep track of whether or not the query is mutating. In case
419-
# of a multi-statement query, the overall query is considered
420-
# mutating if any one of the component statements is mutating
421477
mutating = False
422478

423479
try:
@@ -434,44 +490,11 @@ def one_iteration(text=None):
434490
res = sqlexecute.run(text)
435491
self.formatter.query = text
436492
successful = True
437-
result_count = 0
438-
for title, cur, headers, status in res:
439-
logger.debug("headers: %r", headers)
440-
logger.debug("rows: %r", cur)
441-
logger.debug("status: %r", status)
442-
threshold = 1000
443-
if is_select(status) and cur and cur.rowcount > threshold:
444-
self.echo(
445-
"The result set has more than {} rows.".format(threshold),
446-
fg="red",
447-
)
448-
if not confirm("Do you want to continue?"):
449-
self.echo("Aborted!", err=True, fg="red")
450-
break
451-
452-
if self.auto_vertical_output:
453-
max_width = self.prompt_app.output.get_size().columns
454-
else:
455-
max_width = None
456-
457-
formatted = self.format_output(title, cur, headers, special.is_expanded_output(), max_width)
458-
459-
t = time() - start
460-
try:
461-
if result_count > 0:
462-
self.echo("")
463-
try:
464-
self.output(formatted, status)
465-
except KeyboardInterrupt:
466-
pass
467-
self.echo("Time: %0.03fs" % t)
468-
except KeyboardInterrupt:
469-
pass
470-
471-
start = time()
472-
result_count += 1
473-
mutating = mutating or is_mutating(status)
474493
special.unset_once_if_written()
494+
# Keep track of whether or not the query is mutating. In case
495+
# of a multi-statement query, the overall query is considered
496+
# mutating if any one of the component statements is mutating
497+
mutating = output_res(res, start)
475498
special.unset_pipe_once_if_written()
476499
except EOFError as e:
477500
raise e

litecli/packages/completion_engine.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ def suggest_special(text):
118118
else:
119119
return [{"type": "table", "schema": []}]
120120

121+
if cmd in [".llm", ".ai", "\\llm", "\\ai"]:
122+
return [{"type": "llm"}]
123+
121124
return [{"type": "keyword"}, {"type": "special"}]
122125

123126

litecli/packages/special/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ def export(defn):
1212

1313
from . import dbcommands
1414
from . import iocommands
15+
from . import llm

litecli/packages/special/dbcommands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import platform
77
import shlex
88

9+
910
from litecli import __version__
1011
from litecli.packages.special import iocommands
1112
from .main import special_command, RAW_QUERY, PARSED_QUERY

litecli/packages/special/iocommands.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
from __future__ import unicode_literals
2-
import os
3-
import re
2+
43
import locale
54
import logging
6-
import subprocess
5+
import os
6+
import re
77
import shlex
8+
import subprocess
89
from io import open
910
from time import sleep
1011

1112
import click
1213
import sqlparse
1314
from configobj import ConfigObj
1415

16+
from ..prompt_utils import confirm_destructive_query
1517
from . import export
16-
from .main import special_command, NO_QUERY, PARSED_QUERY
1718
from .favoritequeries import FavoriteQueries
19+
from .main import NO_QUERY, PARSED_QUERY, special_command
1820
from .utils import handle_cd_command
19-
from litecli.packages.prompt_utils import confirm_destructive_query
2021

2122
use_expanded_output = False
2223
PAGER_ENABLED = True
@@ -27,6 +28,8 @@
2728
written_to_pipe_once_process = False
2829
favoritequeries = FavoriteQueries(ConfigObj())
2930

31+
log = logging.getLogger(__name__)
32+
3033

3134
@export
3235
def set_favorite_queries(config):
@@ -95,9 +98,6 @@ def is_expanded_output():
9598
return use_expanded_output
9699

97100

98-
_logger = logging.getLogger(__name__)
99-
100-
101101
@export
102102
def editor_command(command):
103103
"""

0 commit comments

Comments
 (0)