Skip to content

Commit

Permalink
Convert to PHP
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Dec 28, 2020
1 parent 5cc29d5 commit 63abd5f
Show file tree
Hide file tree
Showing 147 changed files with 8,776 additions and 8,274 deletions.
2 changes: 0 additions & 2 deletions .hhconfig

This file was deleted.

64 changes: 1 addition & 63 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,63 +1 @@
# Contributors Guide

Interested in contributing? Awesome! Before you do though, please read our
[Code of Conduct](https://slackhq.github.io/code-of-conduct). We take it very seriously, and expect that you will as
well.

There are many ways you can contribute! :heart:

### Bug Reports and Fixes :bug:

- If you find a bug, please search for it in the [Issues](https://github.com/slackhq/vscode-hack/issues), and if it isn't already tracked,
[create a new issue](https://github.com/slackhq/vscode-hack/issues/new). Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still
be reviewed.
- Issues that have already been identified as a bug (note: able to reproduce) will be labelled `bug`.
- If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number.
- Include tests that isolate the bug and verifies that it was fixed.

### New Features :bulb:

- If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/slackhq/vscode-hack/issues/new).
- Issues that have been identified as a feature request will be labelled `enhancement`.
- If you'd like to implement the new feature, please wait for feedback from the project
maintainers before spending too much time writing the code. In some cases, `enhancement`s may
not align well with the project objectives at the time.

### Tests :mag:, Documentation :books:, Miscellaneous :sparkles:

- If you'd like to improve the tests, you want to make the documentation clearer, you have an
alternative implementation of something that may have advantages over the way its currently
done, or you have any other change, we would be happy to hear about it!
- If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind.
- If not, [open an Issue](https://github.com/slackhq/vscode-hack/issues/new) to discuss the idea first.

If you're new to our project and looking for some way to make your first contribution, look for
Issues labelled `good first contribution`.

## Requirements

For your contribution to be accepted:

- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackhq/vscode-hack).
- [x] The test suite must be complete and pass.
- [x] The changes must be approved by code review.
- [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number.

If the contribution doesn't meet the above criteria, you may fail our automated checks or a maintainer will discuss it with you. You can continue to improve a Pull Request by adding commits to the branch from which the PR was created.

[Interested in knowing more about about pull requests at Slack?](https://slack.engineering/on-empathy-pull-requests-979e4257d158#.awxtvmb2z)

## Creating a Pull Request

1. :fork_and_knife: Fork the repository on GitHub.
2. :runner: Clone/fetch your fork to your local development machine. It's a good idea to run the tests just
to make sure everything is in order.
3. :herb: Create a new branch and check it out.
4. :crystal_ball: Make your changes and commit them locally. Magic happens here!
5. :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-16`).
6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `master` in this
repository.

## Maintainers

There are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md).
TODO
2 changes: 2 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ MIT License

Copyright (c) 2019-present Scott Sandler
Copyright (c) 2019-present Slack Technologies, Inc.
Copyright (c) 2020-present Matt Brown
Copyright (c) 2020-present Vimeo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
117 changes: 6 additions & 111 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Hack SQL Fake
# PHP MySQL Engine

[![Build Status](https://travis-ci.org/slackhq/hack-sql-fake.svg?branch=master)](https://travis-ci.org/slackhq/hack-sql-fake)
This is a PHP port of Slack's [Hack SQL Fake](https://github.com/slackhq/hack-sql-fake) created by [Scott Sandler](https://github.com/ssandler).

Hack SQL Fake is unit testing library for [Hack](https://hacklang.org/). It enables testing database-driven applications with an in-memory simulation of MySQL. It supports a wide variety of queries, rapid snapshot/restore of the database between test cases, and more. This is done with a [Fake Object](https://martinfowler.com/bliki/TestDouble.html), which contains an implementation of the database, avoiding the need for explicit stubbing or mocking.
PHP MySQL Engine is a unit testing library for PHP. It enables testing database-driven applications with an in-memory simulation of MySQL. It supports a wide variety of queries, transactions, and more. This project extends the `PDO` class and allows you to call common PDO MySQL methods.

## Motivation

In most unit testing libraries, SQL queries are traditionally replaced with Mock or Stub implementations. Mocks require an explicit list of queries that are expected to run and results to return, while stubs may not even check the queries being run and simply return a hard coded result. This leads to significant manual work setting up expectations, and tests which are fragile and must be updated even on benign changes to the code or queries. It also means the data access layer is not unit tested.

Another common strategy is to test using an actual database, such as SQLite. This creates a situation in which the database in tests may not match the behavior of the production database, and any code using specialized features of the production database may be untestable. It also means that different test cases are not isolated from each other, which can make tests difficult to debug. This can be resolved by truncating tables between each test case, but that can create a performance problem.

Hack SQL Fake takes a different approach - it parses and executes SELECT, INSERT, UPDATE, and DELETE queries against an in-memory "database" stored in hack arrays. As long as the amount of data used for testing is small, this solves the problems mentioned above.
PHP MySQL Engine takes a different approach - it parses and executes `SELECT`, `INSERT`, `UPDATE`, and `DELETE` queries against an in-memory "database" stored in PHP arrays. As long as the amount of data used for testing is small, this solves the problems mentioned above.

## SQL Syntax Supported

Expand All @@ -31,117 +31,12 @@ For an overview of everything that's supported, see the `tests/` for this librar

## Usage

SQL Fake works by providing a subclass of [AsyncMysqlConnectionPool](https://docs.hhvm.com/hack/reference/class/AsyncMysqlConnectionPool/), the recommended method of querying MySQL built-in to Hack. A subclass for `AsyncMysqlClient` is provided as well.
PHP MySQL Engine works by providing a subclass of [PDO](https://www.php.net/manual/en/class.pdo.php).

This library assumes you currently have some form of establishing a database connection using `AsyncMysqlConnectionPool`. The best way to use SQLFake will depend on your code, but you can use dependency injection or [`fb_intercept()`](https://docs.hhvm.com/hack/reference/function/fb_intercept/) to instantiate a `Slack\SQLFake\AsyncMysqlConnectionPool` when testing. This will behave like a database for the rest of your test run.

Once per test run, you should also call `Slack\SQLFake\init()` to register database schema. See [Exporting Database Schema](#exporting-database-schema) for instructions.

For example, assume you have a class in your source code that manages database connections called `Db` with a method named `getConnectionPool(): AsyncMysqlConnectionPool`. In your tests, you can intercept that function to return an instance of SQLFake's connection pool.

```
$pool = new Slack\SQLFake\AsyncMysqlConnectionPool(darray[]);
fb_intercept('Db::getConnectionPool', (string $name, mixed $obj, array<mixed> $args, mixed $data, bool &$done) ==> {
$done = true;
return $pool;
};
}
```
This library assumes you currently have some form of establishing a database connection using `PDO`. The best way to use PHP MySQL Engine will depend on your code, but you can use dependency injection to instantiate a `Vimeo\MysqlEngine\FakePdo` object when testing. This will behave like a database for the rest of your test run.

The rest of your code can operate as normal, using the database in the same way it is used in production.

## Setup and Teardown

You can use the `Slack\SQLFake\snapshot($name);` and `Slack\SQLFake\restore($name);` functions to make snapshots and restore those snapshots. This can help sharing setup between test cases while isolating database modifications the tests make from other tests. If using HackTest, you may want to call `snapshot()` in `beforeFirstTestAsync` and `restore()` in `beforeEachTestAsync()`.

## Exporting Database Schema

By default, SQL Fake will allow you to summon arbitrary tables into existence with insert statements, without having schema information. However, the library includes a schema exporter which will generate Hack code from `.sql` files. This allows SQL Fake to be much more rigorous, improving the value of tests:

- Validate data types, including nullability
- Fail queries against tables that don't exist
- Fail queries against columns that don't exist
- Enforce primary key and unique constraints
- Implement INSERT ... ON DUPLICATE KEY UPDATE

### Dumping schema to `*.sql` files

The first step to utilizing schema is to create one or sql files containing your schema. You should create one per database (possibly multiple per server) using commands like this:

```
mysqldump -d -u someuser -p mydatabase > mydatabase.sql
mysqldump -d -u someuser -p mydatabase2 > mydatabase2.sql
```

### Generating hack schemas from `*.sql` files

Pass all of the previously-generated SQL files as arguments to `bin/hack-sql-schema` and output the results to any Hack file.

```
bin/hack-sql-schema mydatabase.sql mydatabase2.sql > schema.hack
```

This will generate a file containing a single `const` representing your database schema. You can put this file anywhere you want in your codebase, as the constant it contains can be autoloaded. The file names of the sql files will be used as **database** names, with a shape representing the schema for each table in that database.

### Passing schema to SQLFake

The intended use case for schema is to generate a constant containing the schema and assign it to SQL Fake at startup - but you can choose to do assign it at any time with any value. Assuming you generate a constant using the above scripts named `DB_SCHEMA`, you would assign it to SQLFake like this:

```
Slack\SQLFake\init(DB_SCHEMA);
```

This makes SQLFake aware of which tables and columns exist in which databases. You'll also want to tell it which **servers** contain this information.

## Server configuration

MySQL connections operate on a **host or ip**. Since SQLFake operates at the same level as `AsyncMysqlClient`, it also organizes data by hostname. We recommend using fake hostnames in tests that bear some relationship to the production database infrastructure.

At any time, you can tell SQL Fake about which servers exist and provide settings for those by hostname. Any attempt to connect to servers which haven't been explicitly defined will result in an exception.

```php
Slack\SQLFake\add_server("fakehost1", shape(
// SQL Fake can do some minimal differentiation between 5.6 and 5.7 hosts
'mysql_version' => '5.6',
// SQL Fake can further validate queries that are supported by Vitess databases
'is_vitess' => false,
// On invalid data types, should SQLFake coerce to a valid type or throw? Similar to how MySQL strict mode behaves
'strict_sql_mode' => true,
// If this setting is false, queries that access tables not defined in the schema are allowed
'strict_schema_mode' => true,
// Identify the database name in the schema dictionary to use schema from here
'inherit_schema_from' => 'db1',
));
```

## Metrics

SQL Fake is able to gather metrics about the queries that run during tests, and optionally include stack traces on where those queries ran. This is useful if you'd like to track how many and what kinds of queries are run in key sections of code. This can use a lot of memory, so it is disabled by default.

To enable metrics gathering, set `Slack\SQLFake\Metrics::$enable = true;`. You can then call:

```php
// get the total count of queries that have been invoked
Slack\SQLFake\Metrics::getTotalQueryCount();

// get details on each query that was run
Slack\SQLFake\Metrics::getQueryMetrics();

// get counts by type (insert, update, select, delete)
Slack\SQLFake\Metrics::getCountByQueryType();
```

### Callstacks

When recording information about queries that were invoked in a test, you can capture the callstack at the time the query was run. This can help clarify where those queries are coming from and can be useful to report on which code paths trigger the most queries. To enable callstacks, provide `SQLFake\Metrics::$enableCallstacks = true;`. SQLFake will automatically filter its own functions out of the end of these callstacks. You can also filter out your own low level library code, ensuring the callstacks are most readable, by passing a keyset of patterns to ignore:

```php
Slack\SQLFake\Metrics::\$stackIgnorePatterns = keyset['mysql_*', 'db_*', 'log_*', '_log_*'];
```

Any function names matching those patterns will be removed from the end of the stack trace.

## Why doesn't it support `X`?

This library aims to support everything its users use in MySQL, rather than every possibly feature MySQL offers. We welcome pull requests to add support for new syntax, sql functions, data types, bug fixes, and other features. See our #issues page for a wishlist.
Expand Down
39 changes: 0 additions & 39 deletions bin/hack-sql-schema

This file was deleted.

29 changes: 16 additions & 13 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
{
"name": "slack/hack-sql-fake",
"bin": [
"bin/hack-sql-schema"
],
"description": "Database Testing library for Hacklang",
"name": "vimeo/php-mysql-engine",
"description": "A MySQL engine written in PHP for speeding up tests",
"type": "library",
"authors": [
{
"name": "Scott Sandler",
"email": "ssandler@slack-corp.com"
},
{
"name": "Matt Brown",
"email": "github@muglug.com"
}
],
"require": {
"hhvm/hsl": "=3.30.0",
"hhvm/hsl-experimental": "=3.30.4",
"facebook/hack-codegen": "4.0.2",
"facebook/hh-clilib": "^2.0.0",
"hhvm/hhvm-autoload": "=1.8"
"php": "^7.4|^8",
"ext-pdo": "*",
"phpmyadmin/sql-parser": "^5.4",
"nyholm/dsn": "^2.0"
},
"autoload-dev": {
"psr-4": {
"Vimeo\\MysqlEngine\\": "src"
}
},
"require-dev": {
"hhvm/hhast": "=3.30.0",
"facebook/fbexpect": "^2.2.0",
"hhvm/hacktest": "=1.3"
"vimeo/psalm": "^4.3"
}
}
5 changes: 0 additions & 5 deletions hh_autoload.json

This file was deleted.

11 changes: 0 additions & 11 deletions hhast-lint.json

This file was deleted.

Loading

0 comments on commit 63abd5f

Please sign in to comment.