SQLite is not a toy

Here's why SQLite is perfect for your day-to-day work. It doesn't matter if you are a developer, analyst, tester, admin or product manager.





For starters, a few known facts:





  • SQLite is the most widely used database management system in the world and is included in all popular operating systems.





  • Works without a server.





  • For developers, it is embedded directly into the application.





  • For everyone else - a convenient console (REPL) in one file (sqlite3.exe on Windows, sqlite3 on Linux / macOS).





Console, import and export

The console is a killer feature of SQLite: a more powerful data analysis tool than Excel, and much simpler than any pandas. Data from CSV is loaded with one command, the table is created automatically:





> .import --csv city.csv city
> select count(*) from city;
1117
      
      



Basic SQL features are supported, and the console shows the result in a nice table. There are also advanced SQL features, but more about them later.





select
  century || '- ' as dates,
  count(*) as city_count
from history
group by century
order by century desc;
      
      



โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  dates   โ”‚ city_count โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 21-  โ”‚ 1          โ”‚
โ”‚ 20-  โ”‚ 263        โ”‚
โ”‚ 19-  โ”‚ 189        โ”‚
โ”‚ 18-  โ”‚ 191        โ”‚
โ”‚ 17-  โ”‚ 137        โ”‚
โ”‚ 16-  โ”‚ 79         โ”‚
โ”‚ 15-  โ”‚ 39         โ”‚
โ”‚ 14-  โ”‚ 38         โ”‚
โ”‚ 13-  โ”‚ 27         โ”‚
โ”‚ 12-  โ”‚ 44         โ”‚
โ”‚ 11-  โ”‚ 8          โ”‚
โ”‚ 10-  โ”‚ 6          โ”‚
โ”‚ 9-   โ”‚ 4          โ”‚
โ”‚ 5-   โ”‚ 1          โ”‚
โ”‚ 3-   โ”‚ 1          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      
      



A bunch of data export formats: sql, csv, json, even markdown and html. Everything is done with a couple of commands:





.mode json
.output city.json
select city, foundation_year, timezone from city limit 10;
.shell cat city.json
      
      



[{"city":"","foundation_year":1969,"timezone":"UTC+3"},
{"city":"","foundation_year":1857,"timezone":"UTC+3"},
{"city":"-","foundation_year":1830,"timezone":"UTC+7"},
{"city":"","foundation_year":1913,"timezone":"UTC+7"},
{"city":"","foundation_year":1730,"timezone":"UTC+7"},
{"city":"","foundation_year":1846,"timezone":"UTC+7"},
{"city":"","foundation_year":1709,"timezone":"UTC+7"},
{"city":"","foundation_year":1942,"timezone":"UTC+7"},
{"city":"","foundation_year":1748,"timezone":"UTC+7"},
{"city":"","foundation_year":1736,"timezone":"UTC+7"}]
      
      



Working with JSON native

There is nothing more convenient than SQLite for parsing and transforming JSON. You can select data directly from the file, as if it were a regular table. Or load it into a table and select from there - as you prefer.





select
  json_extract(value, '$.code') as code,
  json_extract(value, '$.name') as name,
  json_extract(value, '$.rate') as rate,
  json_extract(value, '$.default') as "default"
from
  json_each(readfile('currency.sample.json'))
;
      
      



โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ code โ”‚       name        โ”‚    rate    โ”‚ default โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ AZN  โ”‚             โ”‚ 0.023107   โ”‚ 0       โ”‚
โ”‚ BYR  โ”‚   โ”‚ 0.034966   โ”‚ 0       โ”‚
โ”‚ EUR  โ”‚               โ”‚ 0.011138   โ”‚ 0       โ”‚
โ”‚ GEL  โ”‚     โ”‚ 0.0344     โ”‚ 0       โ”‚
โ”‚ KGS  โ”‚      โ”‚ 1.131738   โ”‚ 0       โ”‚
โ”‚ KZT  โ”‚              โ”‚ 5.699857   โ”‚ 0       โ”‚
โ”‚ RUR  โ”‚              โ”‚ 1.0        โ”‚ 1       โ”‚
โ”‚ UAH  โ”‚             โ”‚ 0.380539   โ”‚ 0       โ”‚
โ”‚ USD  โ”‚            โ”‚ 0.013601   โ”‚ 0       โ”‚
โ”‚ UZS  โ”‚       โ”‚ 142.441417 โ”‚ 0       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      
      



It doesn't matter how sprawling the JSON is, you can choose the attributes of any nesting:





select
  json_extract(value, '$.id') as id,
  json_extract(value, '$.name') as name
from
  json_tree(readfile('industry.sample.json'))
where
  path like '$[%].industries'
;
      
      



โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   id   โ”‚         name         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 7.538  โ”‚ -   โ”‚
โ”‚ 7.539  โ”‚ -        โ”‚
โ”‚ 7.540  โ”‚          โ”‚
โ”‚ 9.399  โ”‚        โ”‚
โ”‚ 9.400  โ”‚    โ”‚
โ”‚ 9.401  โ”‚   โ”‚
โ”‚ 43.641 โ”‚                 โ”‚
โ”‚ 43.646 โ”‚           โ”‚
โ”‚ 43.647 โ”‚                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      
      



CTE and set operations

, Common Table Expressions ( WITH



) , . ( parent_id



) โ€”  WITH



. ยซยป  .





with recursive tmp(id, name, level) as (
  select id, name, 1 as level
  from area
  where parent_id is null

  union all

  select
    area.id,
    tmp.name || ', ' || area.name as name,
    tmp.level + 1 as level
  from area
    join tmp on area.parent_id = tmp.id
)

select * from tmp;
      
      



โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  id  โ”‚               name                  โ”‚ level โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 113  โ”‚                               โ”‚ 1     โ”‚
โ”‚ 1    โ”‚ ,                       โ”‚ 2     โ”‚
โ”‚ 1586 โ”‚ ,             โ”‚ 2     โ”‚
โ”‚ 1588 โ”‚ ,  ,    โ”‚ 3     โ”‚
โ”‚ 78   โ”‚ ,  ,    โ”‚ 3     โ”‚
โ”‚ 212  โ”‚ ,  ,  โ”‚ 3     โ”‚
โ”‚ ...  โ”‚ ...                                 โ”‚ ...   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      
      



? : UNION



, INTERSECT



, EXCEPT



.





select employer_id
from employer_area
where area_id = 1

except

select employer_id
from employer_area
where area_id = 2;
      
      



? โ€” :





alter table vacancy
add column salary_net integer as (
  case when salary_gross = true then
    round(salary_from/1.13)
  else
    salary_from
  end
);
      
      



, :





select
  substr(name, 1, 40) as name,
  salary_net
from vacancy
where
  salary_currency = 'RUR'
  and salary_net is not null
limit 10;
      
      



โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   name                   โ”‚ salary_net โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  - (Delphi)     โ”‚ 40000      โ”‚
โ”‚   Scala / Java ( Senio โ”‚ 60000      โ”‚
โ”‚ Java / Kotlin Developer                  โ”‚ 150000     โ”‚
โ”‚   1                      โ”‚ 150000     โ”‚
โ”‚  C#                           โ”‚ 53097      โ”‚
โ”‚  1                           โ”‚ 80000      โ”‚
โ”‚ Java -  (Middle, Senior)      โ”‚ 100000     โ”‚
โ”‚  C#/ .NET                     โ”‚ 70796      โ”‚
โ”‚  / QA engineer (  โ”‚ 45000      โ”‚
โ”‚                                    โ”‚ 17699      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      
      



.

? : , , , . , . , ( ).





.load sqlite3-stats

select
  count(*) as book_count,
  cast(avg(num_pages) as integer) as mean,
  cast(median(num_pages) as integer) as median,
  mode(num_pages) as mode,
  percentile_90(num_pages) as p90,
  percentile_95(num_pages) as p95,
  percentile_99(num_pages) as p99
from books;
      
      



โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ book_count โ”‚ mean โ”‚ median โ”‚ mode โ”‚ p90 โ”‚ p95 โ”‚ p99  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1483       โ”‚ 349  โ”‚ 295    โ”‚ 256  โ”‚ 640 โ”‚ 817 โ”‚ 1199 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      
      



. SQLite - . , โ€”  . .





, . , :





sqlite-plus (GitHub)





. . , :





with slots as (
  select
    num_pages/100 as slot,
    count(*) as book_count
  from books
  group by slot
),
max as (
  select max(book_count) as value
  from slots
)
select
  slot,
  book_count,
  printf('%.' || (book_count * 30 / max.value) || 'c', '*') as bar
from slots, max
order by slot;
      
      



โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ slot โ”‚ book_count โ”‚              bar               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 0    โ”‚ 116        โ”‚ *********                      โ”‚
โ”‚ 1    โ”‚ 254        โ”‚ ********************           โ”‚
โ”‚ 2    โ”‚ 376        โ”‚ ****************************** โ”‚
โ”‚ 3    โ”‚ 285        โ”‚ **********************         โ”‚
โ”‚ 4    โ”‚ 184        โ”‚ **************                 โ”‚
โ”‚ 5    โ”‚ 90         โ”‚ *******                        โ”‚
โ”‚ 6    โ”‚ 54         โ”‚ ****                           โ”‚
โ”‚ 7    โ”‚ 41         โ”‚ ***                            โ”‚
โ”‚ 8    โ”‚ 31         โ”‚ **                             โ”‚
โ”‚ 9    โ”‚ 15         โ”‚ *                              โ”‚
โ”‚ 10   โ”‚ 11         โ”‚ *                              โ”‚
โ”‚ 11   โ”‚ 12         โ”‚ *                              โ”‚
โ”‚ 12   โ”‚ 2          โ”‚ *                              โ”‚
โ”‚ 13   โ”‚ 5          โ”‚ *                              โ”‚
โ”‚ 14   โ”‚ 3          โ”‚ *                              โ”‚
โ”‚ 15   โ”‚ 1          โ”‚ *                              โ”‚
โ”‚ 17   โ”‚ 1          โ”‚ *                              โ”‚
โ”‚ 18   โ”‚ 2          โ”‚ *                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      
      



SQLite ( โ€”  ). INSERT



240 . CSV ( ) โ€”  2 .





.load sqlite3-vsv
create virtual table temp.blocks_csv using vsv(
    filename="ipblocks.csv",
    schema="create table x(network text, geoname_id integer, registered_country_geoname_id integer, represented_country_geoname_id integer, is_anonymous_proxy integer, is_satellite_provider integer, postal_code text, latitude real, longitude real, accuracy_radius integer)",
    columns=10,
    header=on,
    nulls=on
);
      
      



.timer on
insert into blocks
select * from blocks_csv;

Run Time: real 5.176 user 4.716420 sys 0.403866
      
      



select count(*) from blocks;
3386629

Run Time: real 0.095 user 0.021972 sys 0.063716
      
      



, SQLite , . . write-ahead log ( ) . โ€” , .





SQLite . , sqlite.org SQLite , (~200 ). 700 , 95% .





,

, ยซยป . .





SQLite : json json_extract()



:





create table currency(
  body text,
  code text as (json_extract(body, '$.code')),
  name text as (json_extract(body, '$.name'))
);

create index currency_code_idx on currency(code);

insert into currency
select value
from json_each(readfile('currency.sample.json'));
      
      



explain query plan
select name from currency where code = 'RUR';

QUERY PLAN
`--SEARCH TABLE currency USING INDEX currency_code_idx (code=?)
      
      



. WITH RECURSIVE



, :





simple-graph (GitHub)





:





create virtual table books_fts
using fts5(title, author, publisher);

insert into books_fts
select title, author, publisher from books;

select
  author,
  substr(title, 1, 30) as title,
  substr(publisher, 1, 10) as publisher
from books_fts
where books_fts match 'ann'
limit 5;
      
      



โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚       author        โ”‚             title              โ”‚ publisher  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Ruby Ann Boxcar     โ”‚ Ruby Ann's Down Home Trailer P โ”‚ Citadel    โ”‚
โ”‚ Ruby Ann Boxcar     โ”‚ Ruby Ann's Down Home Trailer P โ”‚ Citadel    โ”‚
โ”‚ Lynne Ann DeSpelder โ”‚ The Last Dance: Encountering D โ”‚ McGraw-Hil โ”‚
โ”‚ Daniel Defoe        โ”‚ Robinson Crusoe                โ”‚ Ann Arbor  โ”‚
โ”‚ Ann Thwaite         โ”‚ Waiting for the Party: The Lif โ”‚ David R. G โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
      
      



in-memory ? :





db = sqlite3.connect(":memory:")
      
      



:





db = sqlite3.connect("file::memory:?cache=shared")
      
      



( PostgreSQL). UPSERT



, UPDATE FROM



generate_series()



. R-Tree . , fuzzy- . SQLite ยซยป .





, SQLite . , !





, SQLite โ€” @sqliter








All Articles