GithubHelp home page GithubHelp logo

foundation's Introduction

Foundation

Latest Stable Version Scrutinizer Code Quality Build Status Monthly Downloads License

This is the experimental branch of the foundation component of Pomm Project. It is compatible with PHP version >= 8.0.

What is Foundation ?

Foundation is the main block of Pomm database framework. It makes clients able to communicate either with the database or with each others through a session. One of these clients -- the query manager -- can make Foundation to be used as DBAL replacement. If you are looking for a library to use PostgreSQL in your web development, you might want to look at Pomm’s model manager. If you want to create a custom database access layer or just perform SQL queries, Foundation is the right tool.

Foundation provides out of the box:

  • Converters (all built-in Postgresql types are supported + arrays, HStore etc.) see this SO comment.
  • Prepared Queries.
  • Parametrized queries.
  • Seekable iterators on results.
  • LISTEN / NOTIFY asynchronous messages support.
  • Service manager for easy integration with dependency injection containers.

See more with code examples on this blog post.

Requirements

This branch is tested against the following configurations:

  • PHP 5.6, 7.0, 7.1, 7.2, 7.3 and uses mod-pgsql only (not PDO)
  • Postgresql 9.4

Pomm might work with older versions of PHP or Postgres but this is not tested and can be broken any time.

Installation

Pomm components are available on packagist using composer. To install and use Pomm's foundation, add a require line to "pomm-project/foundation" in your composer.json file.

Note: It is important the PHP configuration file defines the correct timezone setting. Pomm also sets the PostgreSQL connection to this timezone to prevent time shifts between the application and the database.

Documentation

Foundation’s documentation is available either online or directly in the documentation folder of the project.

Tests

This package uses Atoum as unit test framework. The tests are located in sources/tests. The test suite needs to access the database to ensure that read and write operations are made in a consistent manner. You need to set up a database for that and fill the sources/tests/config.php file with the according DSN. For convenience, Foundation provides two classes that extend Atoum with a Session:

  • PommProject\Foundation\Tester\VanillaSessionAtoum
  • PommProject\Foundation\Tester\FoundationSessionAtoum

Making your test class to extend one of these will grant them with a buildSession method that returns a newly created session. Clients of these classes must implement a initializeSession(Session $session) method (even a blank one). It is often a good idea to provide a fixture class as a session client, this method is the right place to register it.

Known bugs

Unfortunately there is a bug we can not fix easily or without degrading performances of the whole stack:

  • The ConvertedResultIterator can not recognize custom composite types when they are defined in schemas other than public. This is because the pg_type function does not return the schema the type belongs to. There are not turns around unless the schema is inspected manually by issuing a lot of queries. (see #53)

foundation's People

Contributors

adrienbrault avatar arno09 avatar artjomsimon avatar bolbo avatar chanmix51 avatar douddnc avatar gnugat avatar gregjarnold avatar jubianchi avatar lunika avatar nedeas avatar npanau avatar ronanguilloux avatar sanpii avatar scrutinizer-auto-fixer avatar seblours avatar stood avatar t0k4rt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

foundation's Issues

Escaping keyword-like column names in prepared query

Hello, I have a following problem with pomm. When I use collumn with name for example "limit" so I end with an error.

The problem i following; Pomm generates prepared query for example:

PREPARE ===
insert into travel_maxima_v1.tariffs ("product", "risk", "limit", "zone", "tariff") values ($1::travel_maxima_v1.product,$2::travel_maxima_v1.risk,$3::travel_maxima_v1.limit,$4::travel_maxima_v1.zone,$5::int4) returning id as id, uid as uid, created_at as created_at, created_by as created_by, updated_at as updated_at, updated_by as updated_by, product as product, risk as risk, limit as limit, zone as zone, tariff as tariff
 ===%

The whole INSERT is properly escaped, bud in the "returning" part is limit as limit. It ends with an error.
Could you fix it?

Thank you.

FlexibleEntity serialization

Tobias Lode wrote:
I started implementing a Pomm 2 bundle based on your repo, already merged in the security layer and user provider of Pomm 1. Now I've got somehow stuck on the JMSSerializer lib, trying to test a rest api with FOSRestBundle and Pomm. I would like to use the exclude/expose annotations on my users entity, but cannot solve the problem, since the properties are held in the fields map. I also tried to exclude/expose by configuration file, but that also didn’t have an effect.
I am curious if you already have a solution on how to accomplish serialization of Pomm 2 entities.

Converter TsRange bug when field is null

The converter bug if the range is null.
With valid bounds it's ok.

I test that with a range field on an entity I aggregate in another entity.

The solution from IRC was ok. (add a trim and compare on string)

vendor/bin/pomm.php is not a php script?

I've installed Pomm 2, as per instructions in "Quick Setup 2.0" on the homepage.

The installed vendor/bin/pomm.php is not a PHP script - it appears to contain a shell script?

As per that page, if I enter php vendor/bin/pomm.php, it simply outputs the contents of the shell script.

I'm on Windows, but I use Git Bash, so I am generally able to run most simple shell scripts.

Handling missing pgsql PHP extension w/ appropriate error message

Scenario:

  • install a pomm-project related project w/ vendors in a valid environment, where Composer requirements check and validate that ext-pgsql is available.
  • move the whole project (sources + vendors) to a new environment, say, a Docker container, where pg-sql extension is missing
  • receive a "Attempted to call function "pg_send_prepare" from namespace PommProject\Foundation\Session" error message

Proposal:

  • Add an appropriate error message about this environment issue, (i. e.: when pgsql ext is missing)

PR to come, feedback/reviews welcome

Expose parameters for converters

With the new converters, we have a complex type.

For the moment, the parameters are return without be prepared.
In profiler, for example, the parameters with complex type are null.

there is two solutions in SimpleQueryManager .

  • The first add a reference at variable parameters in
    protected function doQuery($sql, array &$parameters)
    and
    protected function prepareArguments($sql, array &$parameters)
    and move expose in 'query:post' notification
  • The second is to use prepareArguements again.

What is the best way ?

PostGIS types converters

During the last postgresql meetup, someone talked me about a good improvement: adding postgis converters.

I think this types can be provided by an extension.

I don’t use postgis, but if someone does, it’s a good issue to start contributing to pomm.

http://postgis.net/

Support for DateTimeImmutable

Will there be support for DateTimeImmutable objects in the future?

Currently, Converter\PgTimestamp converts only from DateTime instances (or string representations) and to DateTime instances. Using DateTimeImmutable won't work.

Unfortunately since DateTimeInterface is introduced in PHP 5.5, changing this would break compatibility with PHP 5.4.

Are there any plans to drop PHP 5.4 support in pomm and use DateTimeImmutable instead of DateTime?

Use converter references in Hydration Plan

For now, HydrationPlan caches the ConverterClient instances of converters needed to convert all the values. For performanes reasons, it should be directly the ConverterInterface.

Dissociate credentials from connection parameters

It is often annoying to declare several sessions that share the same connection parameters with different users. They must declare the same session builder every time. What would be nice would be a list of profiles (login, passwords etc.) and a list of session configurations (host, db name, connection settings etc).

$pomm = new Pomm(
    [
        'session_1' =>
            [
                'dsn' => 'pgsql://host:5432/db_name',
                'pomm:default' => true,
                'class:session_builder' => '\Some\Class',
                'default_profile' => 'my_profile',
            ],
        'session_2' => …
    ],
    [
        'my_profile' => ['username' => 'unpriv_user', 'password' => 'p4ßw0rD'],
        'administrator' => ['username' => 'postgres', 'password' => 'p4ßw0rD'],
    ]
    );

$session = $pomm->getDefaultSession(); // Default session (1) with default profile
$session = $pomm['session_1']; // Session 1 with default profile
$session = $pomm->getSession('session_1'); // same as above
$session = $pomm->getSession('session_1', 'administrator'); // session 1 with administrator privileges.

Of course the old form of DSN would still be available and inline the default profile to use.

$pomm = new Pomm(
    [
        'session_1' =>
            [
                'dsn' => 'pgsql://user:p4sS@host:5432/db_name',
            ]
    ],
    [
        'administrator' => ['username' => 'postgres', 'password' => 'p4ßw0rD'],
    ]
);

$session = $pomm->getSession('session_1', 'administrator');

Any thoughts ?

Converter registration in Session fails for repeated registration of any converter

Setup:

  • PHP 5.6
  • Thrift (via overblog/thrift bundle)
  • The thrift server is started as socket server in an event loop

Method (simplified for the use-case):

    /**
     * @param string $name
     *
     * @return bool
     * @author Mario Mueller
     */
    public function existsByName($name)
    {
        $session = $this->db->getSession($this->userSession);
        $userModel = new UsersModel();
        $userModel->initialize($session); // This fails for repeated calls in the same event loop
        return $userModel->existWhere('username=$*', [$name]);
    }

Problem:
Due to the event loop, the session does not get unloaded, so any custom converters are able to be added again. The initialize(Session $session) function mutates the session-related converters, this leads to an exception on the second request to the thrift server.

Desired behaviour:
The session should check for the presence of the converter to add and either skip it or add it and make the list unique afterwards (what ever is more performant).

impossible to retrieve json when using an array_agg

Hi,

In a query I use the array_agg to aggregate a collection of entries, doing that I can retrieve my pomm entites in my php code. The problem is that in this aggregation I have json column and the json has several \ added and produces something like this : ""{\\"price\\":900,\\"foo\\":1,\\"bar\\":\\"month\\"}"" and the PgJson return null because json_decode returns null and no error are checked.

First of all I think that testing if the returned value of json_decode is equal to false is wrong because the documentation says NULL is returned if the json cannot be decoded or if the encoded data is deeper than the recursion limit.. You should test the json_last_error which contains the state of the last json_decode (also if there is no error) :

if (JSON_ERROR_NONE !== json_last_error()) {
  throw new ConverterException(...
}

And for my initial problem I fixed it with a regex but I have no idea if it's a good idea to do that way :

    public function fromPg($data, $type, Session $session)
    {
        if (trim($data) === '') {
            return null;
        }

        if (1 === preg_match('@\\\\\\\\":@', $data)) {
            $data = stripslashes($data);
        }

        $return = json_decode($data, $this->is_array);

        if ($return === false) {
            throw new ConverterException(
                sprintf(
                    "Could not convert Json to PHP %s, driver said '%s'.\n%s",
                    $this->is_array ? 'array' : 'object',
                    json_last_error(),
                    $data
                )
            );
        }

        return $return;
    }

Do you have a better idea how to fix the issue ?

Thanks

Pomm and Symfony existing project

Hi Gregoire,

I ve a Advanced Symfony Project with doctrine and Mysql,
I ve tried Pomm quick set up and could not connect my database with ".pomm_cli_bootstrap.php".
.pomm_cli_bootstrap.php is in the main symfony project.
Issue :
Could not load configuration '.pomm_cli_bootstrap.php'

More informative connection error message.

When the Connection fails, the error message just states the failure but does not give the reason why the attempt fails:

[PommProject\Foundation\Exception\ConnectionException]                                                           
  Error connecting to the database with parameters 'user=xxx dbname=xxxx host=192.168.1.43 port=5439'.

It should be nice to have the reason:

FATAL: role "xxxx" is not permitted to log in.

no converters associated to the regconfig type

The regconfig type can be useful to pass dictionary as parameter to full text search queries. Even though not precising the type of the parameter seems to work, it would be good to enforce the parameter type.

SimpleQuery manager fails with DateTime parameters

When passing a DateTime instance as query parameter, the query fails with the following message

Catchable Fatal Error: Object of class DateTime could not be converted to string in vendor/pomm-project/foundation/sources/lib/Session/Connection.php line 401

Postgresql 12 compatibility

I tried out Postgres 12 beta2 in a project that uses CTEs heavily to see how the removal of the CTE optimization fence affects some performance hotspots.

Pomm's symfony bundle's helper commands bin/console pomm:generate:schema-all fail with Postgres12 for the same reason as Foundation's atoum tests:

...
=> Class PommProject\Foundation\Where: 98.61%
==> PommProject\Foundation\Where::getValues(): 87.50%
> Running duration: 8.18 seconds.
Failure (34 tests, 142/143 methods, 0 void method, 1 skipped method, 0 uncompleted method, 0 failure, 0 error, 1 exception)!
> There is 1 exception:
=> PommProject\Foundation\Test\Unit\Inspector\Inspector::testGetTableFieldInformation():
==> An exception has been thrown in file /home/artjom/code/Foundation/sources/tests/Unit/Inspector/Inspector.php on line 122:
==> PommProject\Foundation\Exception\SqlException: 
==> SQL error state '42703' [ERROR]
==> ====
==> ERROR:  column def.adsrc does not exist
==> LINE 7:     def.adsrc        as "default",
==>             ^
==> 
==> ====
==> «PREPARE ===
==> select
==>     att.attname      as "name",
==>     case
==>         when name.nspname = 'pg_catalog' then typ.typname
==>         else format('%s.%s', name.nspname, typ.typname)
==>     end as "type",
==>     def.adsrc        as "default",
==>     att.attnotnull   as "is_notnull",
==>     dsc.description  as "comment",
==>     att.attnum       as "position",
==>     att.attnum = any(ind.indkey) as "is_primary"
==> from
==>   pg_catalog.pg_attribute att
==>     join pg_catalog.pg_type  typ  on att.atttypid = typ.oid
==>     join pg_catalog.pg_class cla  on att.attrelid = cla.oid
==>     left join pg_catalog.pg_description dsc on cla.oid = dsc.objoid and att.attnum = dsc.objsubid
==>     left join pg_catalog.pg_attrdef def     on att.attrelid = def.adrelid and att.attnum = def.adnum
==>     left join pg_catalog.pg_index ind       on cla.oid = ind.indrelid and ind.indisprimary
==>     left join pg_catalog.pg_namespace name  on typ.typnamespace = name.oid
==> where
==> (att.attrelid = $1 AND att.attnum > 0 AND not att.attisdropped)
==> order by
==>     att.attnum
==>  ===». in /home/artjom/code/Foundation/sources/lib/Session/Connection.php:327
==> Stack trace:
==> #0 /home/artjom/code/Foundation/sources/lib/Session/Connection.php(450): PommProject\Foundation\Session\Connection->getQueryResult('PREPARE ===\nsel...')
==> #1 /home/artjom/code/Foundation/sources/lib/PreparedQuery/PreparedQuery.php(173): PommProject\Foundation\Session\Connection->sendPrepareQuery('7abf55e8ea6de41...', 'select\n    att....')
==> #2 /home/artjom/code/Foundation/sources/lib/PreparedQuery/PreparedQuery.php(124): PommProject\Foundation\PreparedQuery\PreparedQuery->prepare()
==> #3 /home/artjom/code/Foundation/sources/lib/PreparedQuery/PreparedQueryManager.php(38): PommProject\Foundation\PreparedQuery\PreparedQuery->execute(Array)
==> #4 /home/artjom/code/Foundation/sources/lib/Inspector/Inspector.php(444): PommProject\Foundation\PreparedQuery\PreparedQueryManager->query('select\n    att....', Array)
==> #5 /home/artjom/code/Foundation/sources/lib/Inspector/Inspector.php(152): PommProject\Foundation\Inspector\Inspector->executeSql('select\n    att....', Object(PommProject\Foundation\Where))
==> #6 /home/artjom/code/Foundation/sources/tests/Unit/Inspector/Inspector.php(122): PommProject\Foundation\Inspector\Inspector->getTableFieldInformation(399443)
==> #7 /home/artjom/code/Foundation/vendor/atoum/atoum/classes/test.php(1270): PommProject\Foundation\Test\Unit\Inspector\Inspector->testGetTableFieldInformation()
==> #8 Standard input code(1): mageekguy\atoum\test->runTestMethod('testGetTableFie...')
==> #9 {main}

Other projects are having the same issue, it turns out that Postgres 12 dropped the pg_attrdef.adsrc column:

The adsrc field is historical, and is best not used, because it does not track outside changes that might affect the representation of the default value. Reverse-compiling the adbin field (with pg_get_expr for example) is a better way to display the default value.

Following advice in the same thread, I tried the following replacement in the relevant Inspector classes:

-    def.adsrc        as "default",
+    pg_catalog.pg_get_expr(def.adbin, def.adrelid) as "default",

The tests pass with Postgres 12 and my project's schemas get read by pomm. PR incoming.

Compound PrimaryKey with a boolean value are not correctly managed

A primary key was created like this (dumb reasons):

  country      char(3) NOT NULL,
  usePomm      boolean NOT NULL default false,
  [...]
  PRIMARY KEY (country, usePomm)

When doing a findByPk([ 'country' => 'FRA', 'usePomm' => true ]); everything works as expected.
But, when doing a findByPk([ 'country' => 'FRA', 'usePomm' => false ]); an error is thrown :

  SQL error state '22P02' [ERROR]
  ====
  ERROR:  invalid input syntax for type boolean: ""
  ====
  «EXECUTE ===
  select "country" as "country", "usePomm" as "usePomm" from public.test_table where ("country" = $* AND "usePomm" = $*)
   ===
  parameters = {FRA, }».```

Moreover, updateByPk suffers from the same problem.

Cheers

PgComposite not properly escaping parenthesis

} elseif (preg_match('/[,\s]/', $val)) {

I believe the above regex pattern should include a ")" character. If a text value has a closing parenthesis, but does not contain a comma or space, then the parenthesis will not be escaped. This will cause Postgres will assume the tuple has ended, and it will likely cause a SQL error.

Here is the error I received.

[04-Jan-2018 19:23:03 UTC] [emergency] PommProject\Foundation\Exception\SqlException:
SQL error state '22P02' [ERROR]
====
ERROR:  malformed record literal: "(e74212f5-e55d-4e94-a4ff-e8b0c07eb4f4,"",[],<p>this-breaks-it-)</p>,"",f)"
DETAIL:  Too few columns.

It works fine when I make the following change to line 99

} elseif (preg_match('/[,)\s]/', $val)) {

No converter registered for type 'public.citext'

Hello,

I use the extension citext which is not recognized by default in POMM. I have my own sessionBuilder so I had it to don't have an error anymore.

Do you think this type can be added in the foundation SessionBuilder ? Do you consider that type added by a postgresql extension should not be present in the foundation SessionBuilder ?

This is what I made in my SessionBuilder :

protected function initializeConverterHolder(ConverterHolder $converter_holder)
{
    parent::initializeConverterHolder($converter_holder);
    $converter_holder->addTypeToConverter('String', 'public.citext');
    $converter_holder->addTypeToConverter('String', 'citext');
}

Pomm does not set client time zone at start up

When opening a connection, Pomm does not enforce PHP timezone declared in the php.ini config file. This makes the client library to use the system default instead which can create problems with timestamptz values.

Inferring types when values passed as query parameters

There are no mechanisms today to pass complex types as parameter of prepared or parametrized queries.

The obvious example would be using a \DateTime parameter but this cas is handle in a way which is more a hack than a real way to use complex types.

I suggest to adapt the query system to the following:

    $sql = 'select field from my_table where created_at > $*::timestamp';
    $iterator = $session->getQueryManager()->query($sql, [new \DateTime()]);

This does not change the way older queries do work and add extra possibilities:

    $sql = 'select name from station where  position <@ $*::circle';
    $iterator = $session->getQueryManager()->query($sql, [new Circle(…)]);

It is still possible to use «normal» notation:

    $sql = 'select name from station where position <@ circle(point($*, $*), $*)';
    $iterator = $session->getQueryManager()->query($sql, […]);

This feature implies adding an extra method to the ConverterInterface to be able to cast values as short string representation:

    $ts_converter->toPg(new \Datetime, 'timestamp', $session); // returns "timestamp '2015-01-25…'
    $ts_converter->toString(new \DateTime); // returns '2015-01-25…'

    $cl_converter->toPg(new Circle(…), 'circle', $session); // returns 'circle(point(…), …)'
    $cl_converter->toString(new Circle(…)); // returns '<(…), …>'

It may not be mandatory to indicate type as parameter since the type is expected on the other side either in the case of parametrized queries or COPY statements.

Any thoughts ?

Use of own types with schema

Hello,

I have found and another problem. I will use my own types and I will to have they in another schema than "public" that is supported by postgres, but in pomm is a little bug.

There is a following regular expression In the method "getParametersType" of trait "PommProject\Foundation\QueryManager\QueryParameterParserTrait".

preg_match_all('/\$\*(?:::([\w]+(?:\[\])?))?/', $string, $matchs);

But it will not match to type with schema. The correct one should looks like

preg_match_all('/\$\*(?:::([\w]+(?:\[\])?(\.[\w]+)?))?/', $string, $matchs);

It is related with my previous issue #20, because when you will use my type definitions, you will get an error.

PgInterval error

Data 'PT-11H-26M-27.454181S' is not an ISO8601 interval representation.

ConvertedResultIterator does not handle custom types in schemas.

This is mainly due to the use of pg_get_type function in the ResultHandler wrapper class.

Apparently, this function does only output the name of the type regardless of its schema. This makes Pomm to look for a converter declared upon a type without schema prefix which can lead to collisions.

`Pager::getResultCount()` does not return the correct result

The untested (sic) class Pager does return the total result count for both method getResultCount() and getCount() whereas getResultCount() should return only the number of result of the current page (ie the inner iterator).

TODO : fix the bug, write a unit test class for this class.

dependencies in composer.json

I think there is a typo in the require section of the composer.json file.

composer tries to resolve pomm/pomm 2.0.*@dev as dependency but could not resolve it.

pomm-project/pomm-bundle 2.0.x-dev requires pomm/pomm 2.0.*@dev -> no matching package found.

Shouldn't be added pomm-project/{Foundation,ModelManager,Cli} as dependencies?

ConvertedResultIterator::current not expected value

Hello, i have had issue with current() on Iterator, value was not like first child element, but type of data.

$result = current($iterator);
/*
array(7) {
  [0]=>
  string(4) "int8"
  [1]=>
  string(7) "varchar"
  [2]=>
  string(7) "varchar"
  [3]=>
  string(7) "varchar"
  [4]=>
  string(7) "varchar"
  [5]=>
  string(11) "timestamptz"
  [6]=>
  string(11) "timestamptz"
}
*/

But when i use get() method on iterator, the result are well as expected.
weirdly, is same as use in current() method

# PommProject\Foundation\ResultIterator : 111
return $this->isEmpty() ? null : $this->get($this->position);
$results = $iterator->get(0);
/*array(7) {
  ["id"]=>
  int(3)
  ["token"]=>
  string(36) "0ba5bfa8-6398-437b-9cd3-e72f2afe8691"
  ["name"]=>
  string(12) "xxxx"
  ["ip"]=>
  string(13) "xxxx"
  ["description"]=>
  string(3) "xxxx"
  ["created_at"]=>
  object(DateTime)#63 (3) {
    ["date"]=>
    string(26) "2015-04-02 10:14:24.662651"
    ["timezone_type"]=>
    int(1)
    ["timezone"]=>
    string(6) "+02:00"
  }
  ["updated_at"]=>
  object(DateTime)#64 (3) {
    ["date"]=>
    string(26) "2015-04-02 10:14:24.662651"
    ["timezone_type"]=>
    int(1)
    ["timezone"]=>
    string(6) "+02:00"
  }
}
*/

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.