Giter Site home page Giter Site logo

raku-dbsqlite's Introduction

DB::SQLite - SQLite access for Raku

Build Status

This is a reimplementation of Raku bindings for SQLite.

Basic usage

my $s = DB::SQLite.new();  # You can pass in various connection options

Execute a query, and get a single value:

say $s.query('select 42').value;
# 42

Create a table:

$s.execute('create table foo (x int, y text)');

Insert some values using placeholders:

$s.query('insert into foo (x,y) values (?,?)', 1, 'this');

Or even fancy placeholders:

$s.query('insert into foo (x,y) values ($x,$y)', x => 2, y => 'that');

Execute a query returning a row as an array or hash;

say $s.query('select * from foo where x = $x', :x(1)).array;
say $s.query('select * from foo where x = $x', :2x).hash;

Execute a query returning a bunch of rows as arrays or hashes:

.say for $s.query('select * from foo').arrays;
.say for $s.query('select * from foo').hashes;

Connection Information

When you create a DB::SQLite object, you can specify a filename option to .new for the database to open. If it isn't specified, it will default to an empty string which causes a private, temporary on-disk database to be created. This will be useless if you use more than one connection, since each will get its own database, but maybe you want that..

If you specify filename => ':memory' you will get a private, temporary, in-memory database. Again, this will not be shared across connections.

You can also use a busy-timeout option to specify in milliseconds, the amount of sleeping to wait for a locked table to become available. This defaults to 10000 (10 seconds). Setting to zero will turn off busy handling.

use DB::SQLite;

my $s = DB::SQLite.new(filename => 'this.db', busy-timeout => 50000);

You can also pass in additional extended open flags, such as 'readonly':

my $s = DB::SQLite.new(filename => 'this.db', :readonly);

Depending on your sqlite version, Flags may include: readonly, readwrite, create, deleteonclose, exclusive, autoproxy, uri, memory, main_db, temp_db, transient_db, main_journal, temp_journal, subjournal, super_journal, nomutex, fullmutex, sharedcache, privatecache, wal, nofollow. Refer to the SQLite docs for more information on them.

DB::SQLite::Connection

The main DB::SQLite object acts as a factory for connections, maintaining a cache of connections already created. A new connection can be requested with the .db method, but often this isn't needed. When you are finished with a connection, you can explicitly return it to the cache with .finish.

You can call .query() or .execute() on the main DB::SQLite object, but all they really do is allocate a DB::SQLite::Connection (either from the cache, or create a new one) and call those methods on it, then return the connection to the cache.

These are equivalent:

.say for $s.query('select * from foo').arrays;
my $db = $s.db;
.say for $db.query('select * from foo').arrays;
$db.finish;

The connection object also has some extra methods for separately preparing and executing the query:

my $db = $s.db;
my $sth = $db.prepare('insert into foo (x,y) values (?,?)');
$sth.execute(1, 'this');
$sth.execute(2, 'that');
$db.finish;

You can also call .finish() on the statement:

my $sth = $s.db.prepare('insert into foo (x,y) values (?,?)');
$sth.execute(1, 'this');
$sth.execute(2, 'that');
$sth.finish;

The statement will finish the associated connection, returning it to the cache. Yet another way to do it is to pass :finish in to the execute.

my $sth = $s.db.prepare('insert into foo (x,y) values (?,?)');
$sth.execute(1, 'this');
$sth.execute(2, 'that', :finish);

And finally, a cool Perl 6ish way is the will trait to install a Phaser directly on the variable:

{
    my $sth will leave { .finish } = $s.db.prepare('insert into foo (x,y) values (?,?)');
    $sth.execute(1, 'this');
    $sth.execute(2, 'that');
}

Calling .prepare() on the DB::SQLite::Connection prepares and returns a DB::SQLite::Statement that can then be .execute()ed. The prepared statement is also retained in a cache with the connection. If the same statement is prepared again on the same connection, the cached object will be returned instead of re-preparing. If you don't want it to be cached, you can pass in the :nocache option.

my $sth = $s.db.prepare('insert into foo (x,y) values (?,?)', :nocache);
$sth.execute(1, 'this');
$sth.execute(2, 'that', :finish);

You must still take care to call .finish() to return the connection to the connection cache so it will get reused. (Or take care NOT to call .finish() if you don't want the connection to be reused, possibly in another thread.)

For the main object, or the connection object, .execute() is used instead of .query() under two conditions:

  1. You don't need placeholders/arguments.
  2. You don't want the results.

As a special added bonus you can execute multiple statements separated by semi-colons in one shot:

$s.execute(q:to/END/);
create table foo
(
   x int,
   y text
);
insert into foo (x,y) values (1, 'this');
insert into foo (x,y) values (2, 'that');
END

Transactions

The database connection object can also manage transactions with the .begin, .commit, and .rollback methods:

my $db = $s.db;
my $sth = $db.prepare('insert into foo (x,y) values (?,?)');
$db.begin;
$sth.execute(1, 'this');
$sth.execute(2, 'that');
$db.commit;
$db.finish;

The begin/commit ensure that the statements between them happen atomically, either all or none.

Transactions can also dramatically improve performance for some actions, such as performing thousands of inserts/deletes/updates since the indexes for the affected table can be updated in bulk once for the entire transaction.

If you .finish the database prior to a .commit, an uncommitted transaction will automatically be rolled back.

As a convenience, .commit also returns the database object, so you can just $db.commit.finish.

Placeholders and Binding

SQLite parameters can take several different forms:

  • ?
  • ?NNN
  • :AAA
  • $AAA
  • @AAA

Where NNN is an integer value, and AAA is an identifier. When calling execute, the numbered binds are bound starting with 1 from the arguments to .execute (or .query):

my $sth = $s.db.prepare('select ?1, ?2, ?3');
say $sth.execute(1,2,3).array;
$sth.finish;

The named binds with $AAA placeholders are bound with named parameter pairs:

my $sth = $s.db.prepare('select $x, $y, $z');
say $sth.execute(:x(1), :y(2), :z(3)).array;
say $sth.execute(x => 1, y => 2, z => 3).array; # same thing
$sth.finish;

Binding the other placeholders is a little more complicated. They must be bound explicitly prior to calling .execute() (This will work with numbered placeholders too.):

my $sth = $s.db.prepare('select :x, $y, @z');
$sth.bind(':x', 1)
$sth.bind('$y', 2)
$sth.bind('@z', 3)
$sth.execute();
$sth.finish;

You don't have to bind every placeholder. If you leave one out, it just gets a NULL. If you .execute multiple times with the same statement, it will use whatever bindings are in place from previous executions. Since by default, statements get cached and re-used, the safest approach is always to bind every placeholder, even ones you want to be NULL. (Bind with an undefined type, such as Any for NULL).

You can even mix and match numbered and named placeholders if you want to (and are careful).

Results

Calling .query() on a DB::SQLite or DB::SQLite::Connection, or calling .execute() on a DB::SQLite::Statement with an SQL SELECT or something that returns data, a DB::SQLite::Result object will be returned.

The query results can be consumed from that object with the following methods:

  • .value - a single scalar result
  • .array - a single array of results from one row
  • .hash - a single hash of results from one row
  • .arrays - a sequence of arrays of results from all rows
  • .hashes - a sequence of hashes of results from all rows

If the query isn't a select or otherwise doesn't return data, such as an INSERT, UPDATE, or DELETE, it will return the number of rows affected.

Exceptions

All database errors, including broken SQL queries, are thrown as exceptions.

Acknowledgements

Inspiration taken from the existing Raku DBIish module as well as the Perl 5 Mojo::Pg from the Mojolicious project.

Thanks to hythm7 for contributing to the open flags capability.

License

Portions thanks to DBIish:

Copyright © 2009-2016, the DBIish contributors All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

raku-dbsqlite's People

Contributors

curttilmes avatar melezhik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

raku-dbsqlite's Issues

prepare statements don't protect apostrophes

I thought by using a prepared statement with ? placeholders I wouldn't have to be concerned about taking care of embedded apostrophes. Something like this doesn't work as expected:

my $last = "O'Connor";
my $sth = $s.db.prepare('insert into person_tbl (last) values (?)');
$sth.execute($last);
BOOM

DB::SQLite doesn't like IntStr placeholders

DB::SQLite dies when passing an IntStr value as a placeholder. Most likely scenario is when passing a parameter on the command line via sub MAIN.

Example:

#!/usr/bin/env perl6
use v6.c;

use DB::SQLite;

my $db = DB::SQLite.new;

sub MAIN(Int $foo)
{
    say $db.query('SELECT 1 WHERE ? = ?', $foo, $foo).value;
}

This dies with:

Ambiguous call to 'AUTOGEN(DB::SQLite::Native::Statement: Int, IntStr)'; these signatures all match:
:(DB::SQLite::Native::Statement: Int $n, Int:D $v, *%_ --> Nil)
:(DB::SQLite::Native::Statement: Int $n, Str:D $v, *%_ --> Nil)
  in method execute at /opt/perl6-2018.10/share/perl6/site/sources/F735A230CF27D032781E817AC2AB5AC529C14695 (DB::SQLite::Statement) line 25
  in method query at /opt/perl6-2018.10/share/perl6/site/sources/F1B43C2361FC6DB5F165CBD906DB9F08A144DB8A (DB::Connection) line 48
  in method query at /opt/perl6-2018.10/share/perl6/site/sources/51223C7606846125E36A7A53A8884E0090F594CE (DB) line 21
  in sub MAIN at ./sqlite-test line 8
  in block <unit> at ./sqlite-test line 8

A workaround, of course, is to make sure all values are Ints:

    say $db.query('SELECT 1 WHERE ? = ?', +$foo, +$foo).value;

But it would be good if it wasn't necessary to work around this. I think it's safe to assume that an IntStr holds a number.

doc error in Result.pm6

In file lib/DB/SQLite/Result.pm6 there is a MySql reference at line 52:

DB::MySQL::Result -- Results from a MySQL query

Out of memory when invoking several times .hashes

Hi,

I just got an error "Out of memory". The SQLite file is 368M.

Steps:

> raku
To exit type 'exit' or '^D'
> use DB::SQLite;
Nil
> my $dbh = DB::SQLite.new(filename => 'data/proxy.sqlite');
DB::SQLite.new(filename => "data/proxy.sqlite", busy-timeout => 10000, max-connections => 5, connections => Concurrent::Stack.new)
> my $result = $dbh.query('select * from requests limit 1');
DB::SQLite::Result.new(stmt => DB::SQLite::Native::Statement.new, count => 10, sth => DB::SQLite::Statement.new(stmt => DB::SQLite::Native::Statement.new, count => 10, db => DB::SQLite::Connection.new(conn => DB::SQLite::Native.new, owner => DB::SQLite.new(filename => "data/proxy.sqlite", busy-timeout => 10000, max-connections => 5, connections => Concurrent::Stack.new))), finish => Bool::True, keys-cache => Any)
> $result.hash
{cache => 86400, content_type => image/jpeg, host => images-webcams.windy.com, id => 1, method => GET, port => 443, protocol => HTTP/1.1, raw_response => Buf[uint8]:0x<48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D 0A 53 65 72 76 65 72 3A 20 6E 67 69 6E 78 2F 31 2E 31 30 2E 33 0D 0A 44 61 74 65 3A 20 53 75 6E 2C 20 33 31 20 4D 61 79 20 32 30 32 30 20 31 32 3A 30 36 3A 31 33 20 47 4D 54 0D 0A 43 6F 6E 74 65 6E 74 2D 54 79 70 65 3A 20 69 6D 61 67 65 2F 6A 70 65 67 ...>, status => 200 OK, uri => /16/1292677816/daylight/thumbnail/1292677816.jpg}
> $result.hashes
()
> say $_<content_type> for $result.hashes
image/jpeg
> say $_<content_type> for $result.hashes
image/jpeg
> say $_<content_type> for $result.hashes
image/jpeg
> say $_<content_type> for $result.hashes
image/jpeg
> say $_<content_type> for $result.hashes
out of memory
  in block  at /home/demanuel/.raku/sources/3481BCAD53FC3338AC505479B91D3AD1F5BF3724 (DB::SQLite::Result) line 42
  in method row at /home/demanuel/.raku/sources/3481BCAD53FC3338AC505479B91D3AD1F5BF3724 (DB::SQLite::Result) line 27
  in method pull-one at /home/demanuel/.raku/sources/51F6397FB5D02B2A0E15A62EC3ACF47669DBF3DC (DB::Result) line 21
  in block <unit> at <unknown file> line 1

> say $_<content_type> for $result.hashes
out of memory
  in block  at /home/demanuel/.raku/sources/3481BCAD53FC3338AC505479B91D3AD1F5BF3724 (DB::SQLite::Result) line 42
  in method row at /home/demanuel/.raku/sources/3481BCAD53FC3338AC505479B91D3AD1F5BF3724 (DB::SQLite::Result) line 27
  in method pull-one at /home/demanuel/.raku/sources/51F6397FB5D02B2A0E15A62EC3ACF47669DBF3DC (DB::Result) line 21
  in block <unit> at <unknown file> line 1

> 

can't use a named bind parameters with execute

The method signature of execute

    method execute(Bool :$finish, *@args, *%args)

is such that the third parameter (%args) is never going to be populated. *@args always steals the data that would be caught by *%args.

As a result it always raises an error if you try to call it with a hash parameter, because it uses the list block instead of the named parameter block.

here's a little test routine from in the REPL to prove this.

[253] > sub foo(Bool :$finish, *@args, *%args) { if @args { say "@args";}; if %args { say "%args";}}
&foo
[254] > foo(@list);
@args
[254] > foo(%hash);
@args
[254] > foo(@list, %hash);
@args

on a related note: the docs for this method don't match the signature of the method

=head2 B<execute>(**@args, Bool :$finish)

Native lib name sqlite3 vs sqlite

looks like the library name on latest ubuntu is libsqlite3.so.

> use NativeLibs; NativeLibs::Searcher.try-versions('sqlite', 'sqlite3_libversion', 0);
(Any)
> use NativeLibs; NativeLibs::Searcher.try-versions('sqlite3', 'sqlite3_libversion', 0);
libsqlite3.so.0

May be the dependency name in META6.json should be changed to sqlite3:from< native > ?

another alternative would be (based on this comment ) :

{ "any": [ "sqlite3:from< native >", "sqlite:from< native >"] }

although this might be considered abusing the alternative dependencies suggestion.

The module fails to install due to hard-coded NativeLibs version

NativeLibs has been updated recently to v0.0.8. DB::SQLite depends specifically on v0.0.7 and consequently fails to install since Oct 16 when the dependency has been updated. I tried removing version adverb from the use statement in the code and testing. Tests are passing ok. Perhaps it worth leaving the version in META6 only and have it in 0.0.7+ form?

sqlite3 column names "oid" and "_rowid_" are not returned in a results hash

Quoting from the SQLIte docs:

Except for WITHOUT ROWID tables, all rows within SQLite tables have a 64-bit signed
integer key that uniquely identifies the row within its table. This integer is usually
called the "rowid". The rowid value can be accessed using one of the special
case-independent names "rowid", "oid", or "_rowid_" in place of a column name. 

DB::SQLite does recognize "oid" or "_rowid_" in a select query, but neither shows up as a hash key when used. Only the %hash<rowid> key is recognized regardless of the column name used in the select query.

Frequent "Database is locked" errors

I'm writing an application using DB::SQLite to access a database from a Cro HTTP service and another standalone daemon. I'm hitting a surprising amount of "Database is locked" errors with non-obvious causes.

I've managed to replicate this with this test program (run concurrently):

use DB::SQLite;

sub MAIN() {
    my $path = 'tmp.sqlite';
    my $exists = $path.IO ~~ :e;
    my $db = DB::SQLite.new(filename => $path);

    unless $exists {
        say "setting up test data";
        $db.query('CREATE TABLE t(foo, bar)');
        $db.query('INSERT INTO t(foo, bar) values(?, ?)', 1, 2);
    }

    # DB gets locked here
    say ">> running a query that will lock the database";
    my $value = $db.query('SELECT * FROM t WHERE foo = ?', 1).hash;

    # demonstrate the lock with any other write operation
    say ">> running another write query";
    $db.query('INSERT INTO t(foo, bar) values(?, ?)', 2, 3);

    say ">> waiting...";
    sleep 100;
}

This reliably causes a "database is locked" error if I run the script in one terminal, and then run a second copy of the script while the first is waiting.

I'm not sure if this is a usage error on my side, but it seems surprisingly easy to get the database stuck like this.

The Perl 5 implementation of my application runs reliably with DBD::SQLite - though the usage pattern there is very standardized (almost always prepare, execute, return fetchall_arrayref({}), though no explicit finish calls).

I'm planning to move this application to Postgres for other reasons (primarily notify support), but I wanted to provide some feedback. Thanks.

Rakudist test fails

Hi! I wonder, why zef complaints about sqlite dependencies, even after it's installed?

*** patch dir: /root/.sparrowdo/env/debian/.sparrowdo/files/patches/DB::SQLite, patch found ***
*** execute patch from /root/.sparrowdo/env/debian/.sparrowdo/files/patches/DB::SQLite/sparrowfile ***
15:15:19 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] trying to install libsqlite3-dev ...
15:15:19 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] installer - apt-get
15:15:19 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Package: libsqlite3-dev
15:15:19 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Version: 3.27.2-3
15:15:19 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Status: install ok installed
[task check] stdout match <Status: install ok installed> True
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] trying to install sqlite3 ...
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] installer - apt-get
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Package: sqlite3
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Version: 3.27.2-3
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Status: install ok installed
[task check] stdout match <Status: install ok installed> True
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] trying to install sqlite ...
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] installer - apt-get
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Package: sqlite
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Version: 2.8.17-15
15:15:20 10/29/2020 [install package(s): {:debian("libsqlite3-dev sqlite3 sqlite")}] Status: install ok installed
[task check] stdout match <Status: install ok installed> True
15:15:25 10/29/2020 [bash: zef install DB::SQLite] ===> Searching for missing dependencies: BitEnum, DB, NativeLibs:ver<0.0.7+>:auth<github:salortiz>, sqlite:from<native>, Test::When
15:15:58 10/29/2020 [bash: zef install DB::SQLite] ===> Failed to find dependencies: sqlite:from<native>
15:15:58 10/29/2020 [bash: zef install DB::SQLite] stderr: Failed to resolve some missing dependencies
15:15:58 10/29/2020 [bash: zef install DB::SQLite] task exit status: 1
15:15:58 10/29/2020 [bash: zef install DB::SQLite] task bash: zef install DB::SQLite FAILED
Error: non zero exit code: 1: OCI runtime error
The spawned command 'podman exec -it debian-rakudist sh /root/.sparrowdo/env/debian/.sparrowdo/sparrowrun.sh' exited unsuccessfully (exit code: 1, signal: 0)
  in block <unit> at /home/rakudist/.rakubrew/versions/moar-2020.05.1/share/perl6/site/resources/B048A8B0FC7EDBE1A75DF9FA9EA11DCB8F98BFF7 line 14
  in sub MAIN at /home/rakudist/.rakubrew/versions/moar-2020.05.1/share/perl6/site/bin//sparrowdo line 3
  in block <unit> at /home/rakudist/.rakubrew/versions/moar-2020.05.1/share/perl6/site/bin//sparrowdo line 1

The full log is available here - http://rakudist.raku.org/sparky/report/debian/825

HTH

Alexey

In memory database support broken

my $s = DB::SQLite.new(filename => ':memory');
$s.execute('create table foo (x int, y text)');

This code writes a database to a file named ':memory' instead of in memory.

Found this out by trying to use the in-memory support for a test file.

add query method .values

The query has a .value method for returning a single value when only one row is expected to satisfy the query.

Shouldn't there be an equivalent .values method returning an array of values when multiple rows are expected to be affected?

For example:

SELECT rowid FROM blah
ORDER BY rowid

Use of Nil in numeric context in DB::SQLite::Statement line 44

I'm using DB::SQLite in a project, and one particular statement I'm trying to execute is throwing this error.

Use of Nil in numeric context
  in block  at /home/tyil/.rakudo-star/rakudo-star-2019.03/install/share/perl6/site/sources/A96187CD3259EABBAF49C3BE8984779C46DABD52 (DB::SQLite::Statement) line 44
No such method 'hashes' for invocant of type 'Slip'. Did you mean 'hash'?
  in method get-next-for-project at /home/tyil/projects/personal/gtd-api/lib/App/GTD/Models/Sqlite/Thing.pm6 (App::GTD::Models::Sqlite::Thing) line 152
  in method next at /home/tyil/projects/personal/gtd-api/lib/App/GTD/Models/Sqlite/Project.pm6 (App::GTD::Models::Sqlite::Project) line 84
  in sub MAIN at /home/tyil/projects/personal/gtd-api/lib/App/GTD/Bin/ListProjects.pm6 (App::GTD::Bin::ListProjects) line 30
  in block <unit> at bin/gtd line 11

Looking at the source, it looks like .step is being set to Nil somewhere, though I don't have an idea how that could happen.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.