├── .github
└── workflows
│ └── checks.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── NOTICE
├── README.md
├── bin
└── php
├── composer.json
├── examples
└── write_read.php
├── lib
├── avro.php
└── avro
│ ├── data_file.php
│ ├── datum.php
│ ├── debug.php
│ ├── gmp.php
│ ├── io.php
│ ├── protocol.php
│ ├── schema.php
│ └── util.php
├── phpunit.xml.dist
└── test
├── DataFileTest.php
├── DatumIOTest.php
├── FileIOTest.php
├── FloatIntEncodingTest.php
├── IODatumReaderTest.php
├── LongEncodingTest.php
├── NameTest.php
├── ProtocolFileTest.php
├── SchemaTest.php
├── StringIOTest.php
├── data
├── user.avsc
├── users.avro
├── users.json
├── users_deflate.avro
└── users_snappy.avro
└── test_helper.php
/.github/workflows/checks.yml:
--------------------------------------------------------------------------------
1 | name: avro-php
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - master
7 | jobs:
8 | build-source:
9 | runs-on: ubuntu-22.04
10 | steps:
11 | -
12 | name: Checkout
13 | uses: actions/checkout@v4
14 | -
15 | name: Install phars
16 | run: |
17 | make install-phars
18 | -
19 | name: Upload source directory
20 | uses: actions/upload-artifact@v4
21 | with:
22 | name: source
23 | include-hidden-files: true
24 | path: .
25 | php-xdebug-docker:
26 | needs:
27 | - build-source
28 | strategy:
29 | matrix:
30 | php:
31 | -
32 | version: '8.1'
33 | xdebug: '3.4.0'
34 | -
35 | version: '8.2'
36 | xdebug: '3.4.0'
37 | -
38 | version: '8.3'
39 | xdebug: '3.4.0'
40 | -
41 | version: '8.4'
42 | xdebug: '3.4.0'
43 | runs-on: ubuntu-22.04
44 | steps:
45 | -
46 | name: Download sources
47 | uses: actions/download-artifact@v4
48 | with:
49 | name: source
50 | -
51 | name: Set up Docker Buildx
52 | uses: docker/setup-buildx-action@v3
53 | -
54 | name: Build
55 | uses: docker/build-push-action@v6
56 | with:
57 | context: .
58 | file: ./Dockerfile
59 | load: true
60 | tags: avro-php:${{ matrix.php.version }}
61 | build-args: |
62 | PHP_VERSION=${{ matrix.php.version }}
63 | XDEBUG_VERSION=${{ matrix.php.xdebug }}
64 | -
65 | name: Inspect docker image
66 | run: |
67 | docker image inspect avro-php:${{ matrix.php.version }}
68 | -
69 | name: Save docker image
70 | run: |
71 | docker save avro-php:${{ matrix.php.version }} -o php-avro-serde-${{ matrix.php.version }}.tgz
72 | -
73 | name: Upload docker image
74 | uses: actions/upload-artifact@v4
75 | with:
76 | name: php-avro-serde-${{ matrix.php.version }}
77 | path: php-avro-serde-${{ matrix.php.version }}.tgz
78 | ci-checks:
79 | runs-on: ubuntu-22.04
80 | needs:
81 | - php-xdebug-docker
82 | strategy:
83 | matrix:
84 | php:
85 | -
86 | version: '8.1'
87 | composer: --prefer-stable
88 | -
89 | version: '8.2'
90 | composer: --prefer-stable
91 | -
92 | version: '8.3'
93 | composer: --prefer-stable
94 | -
95 | version: '8.4'
96 | composer: --prefer-stable
97 | steps:
98 | -
99 | name: Download sources
100 | uses: actions/download-artifact@v4
101 | with:
102 | name: source
103 | -
104 | name: Set up Docker Buildx
105 | uses: docker/setup-buildx-action@v3
106 | -
107 | name: Download docker image
108 | uses: actions/download-artifact@v4
109 | with:
110 | name: php-avro-serde-${{ matrix.php.version }}
111 | -
112 | name: Load docker image
113 | run: |
114 | docker load -i php-avro-serde-${{ matrix.php.version }}.tgz
115 | -
116 | name: Install vendors
117 | run: |
118 | docker run -i --rm --net=host --sig-proxy=true --pid=host \
119 | -v "${GITHUB_WORKSPACE}":"${GITHUB_WORKSPACE}" -w "${GITHUB_WORKSPACE}" avro-php:${{ matrix.php.version }} \
120 | composer update --no-interaction --no-scripts --no-ansi ${{ matrix.php.composer }}
121 | -
122 | name: Run PHPUnit
123 | run: |
124 | mkdir -p build/tmp build/share/test/schemas build/build/interop/data
125 | chmod -R a+w build
126 | docker run -i --rm --net=host --sig-proxy=true --pid=host \
127 | -v "${GITHUB_WORKSPACE}":"${GITHUB_WORKSPACE}" -w "${GITHUB_WORKSPACE}" avro-php:${{ matrix.php.version }} \
128 | vendor/bin/phpunit --exclude-group integration
129 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | vendor/
3 | composer.lock
4 | bin/composer.phar
5 | !bin/php
6 | !build/.gitkeep
7 | .phpunit.result.cache
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: required
3 | language: php
4 |
5 | php:
6 | - '7.1'
7 | - '7.2'
8 | - '7.3'
9 | - '7.4'
10 |
11 | before_script:
12 | - cd /tmp
13 | - git clone --recursive --depth=1 https://github.com/kjdev/php-ext-snappy.git
14 | - cd php-ext-snappy
15 | - export CXXFLAGS=-std=c++11
16 | - phpize
17 | - ./configure
18 | - make
19 | - make install
20 | - echo "extension = snappy.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
21 | - cd $TRAVIS_BUILD_DIR
22 | - composer self-update
23 | - composer update
24 |
25 | script:
26 | - COMPOSER=composer PHP=php make phpunit
27 |
28 | after_script:
29 | - make clean
30 |
31 | branches:
32 | only:
33 | - master
34 | - '/^ft-.*/'
35 | - '/^\d+\.\d+\.\d+$/'
36 |
37 | cache:
38 | directories:
39 | - $HOME/.composer/cache/files
40 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG PHP_VERSION=8.1
2 |
3 | FROM php:${PHP_VERSION}-cli-alpine
4 |
5 | ARG XDEBUG_VERSION=3.4.0
6 |
7 | COPY --from=composer /usr/bin/composer /usr/bin/composer
8 | RUN composer --version
9 |
10 | RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \
11 | && apk add --no-cache --virtual .runtime-deps git libzip-dev gmp-dev \
12 | && docker-php-source extract \
13 | && docker-php-ext-configure zip \
14 | && docker-php-ext-install zip gmp \
15 | && apk add --update linux-headers \
16 | && mkdir -p /usr/src/php/ext/xdebug \
17 | && curl -fsSL https://github.com/xdebug/xdebug/archive/$XDEBUG_VERSION.tar.gz | tar xvz -C /usr/src/php/ext/xdebug --strip 1 \
18 | && docker-php-ext-install xdebug \
19 | && docker-php-ext-enable xdebug \
20 | && git clone --recursive --depth=1 https://github.com/kjdev/php-ext-snappy.git \
21 | && cd php-ext-snappy \
22 | && phpize \
23 | && ./configure \
24 | && make \
25 | && make install \
26 | && docker-php-ext-enable snappy \
27 | && docker-php-source delete \
28 | && apk del .build-deps
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
204 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # no builtin rules and variables
2 | MAKEFLAGS =+ -rR --warn-undefined-variables
3 |
4 | .PHONY: composer-install composer-update examples docker run
5 |
6 | COMPOSER ?= bin/composer.phar
7 | COMPOSER_VERSION ?= 2.8.4
8 | PHP ?= bin/php
9 | PHP_VERSION ?= 8.3
10 | XDEBUG_VERSION ?= 3.4.0
11 |
12 | export
13 |
14 | docker:
15 | docker build \
16 | --build-arg PHP_VERSION=$(PHP_VERSION) \
17 | --build-arg XDEBUG_VERSION=$(XDEBUG_VERSION) \
18 | -t avro-php:$(PHP_VERSION) \
19 | -f Dockerfile \
20 | .
21 |
22 | composer-install:
23 | PHP_VERSION=$(PHP_VERSION) $(PHP) $(COMPOSER) install --no-interaction --no-progress --no-suggest --no-scripts
24 |
25 | composer-update:
26 | PHP_VERSION=$(PHP_VERSION) $(PHP) $(COMPOSER) update --no-interaction --no-progress --no-suggest --no-scripts
27 |
28 | phpunit:
29 | @mkdir -p build/tmp build/share/test/schemas build/build/interop/data
30 | @chmod -R a+w build
31 | PHP_VERSION=$(PHP_VERSION) $(PHP) vendor/bin/phpunit
32 |
33 | coverage:
34 | @mkdir -p build/tmp build/share/test/schemas build/build/interop/data
35 | @chmod -R a+w build
36 | PHP_VERSION=$(PHP_VERSION) $(PHP) -d xdebug.mode=coverage vendor/bin/phpunit --coverage-text
37 |
38 | run:
39 | PHP_VERSION=$(PHP_VERSION) $(PHP) $(ARGS)
40 |
41 | examples:
42 | PHP_VERSION=$(PHP_VERSION) $(PHP) examples/*
43 |
44 | install-phars:
45 | curl https://getcomposer.org/download/$(COMPOSER_VERSION)/composer.phar -o bin/composer.phar -LR -z bin/composer.phar
46 | chmod a+x bin/composer.phar
47 |
48 | install: install-phars docker composer-install
49 |
50 | clean:
51 | rm -r build/*
52 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Apache Avro
2 | Copyright 2010-2015 The Apache Software Foundation
3 |
4 | This product includes software developed at
5 | The Apache Software Foundation (http://www.apache.org/).
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | What the Avro PHP library is
2 | ============================
3 |
4 | A library for using [Avro](http://avro.apache.org/) with PHP.
5 |
6 | Requirements
7 | ============
8 | * PHP >= 8.1
9 | * On 32-bit platforms, the [GMP PHP extension](http://php.net/gmp)
10 | * For testing, [PHPUnit](http://www.phpunit.de/)
11 | * For Deflate codec, [Zlib](https://www.php.net/zlib)
12 | * For Snappy codec, [Snappy](http://google.github.io/snappy/)
13 |
14 | Both GMP and PHPUnit are often available via package management
15 | systems as `php5-gmp` and `phpunit`, respectively.
16 |
17 | Getting started
18 | ===============
19 |
20 | Untar the avro-php distribution, untar it, and put it in your include path:
21 |
22 | tar xjf avro-php.tar.bz2 # avro-php.tar.bz2 is likely avro-php-1.4.0.tar.bz2
23 | cp avro-php /path/to/where/you/want/it
24 |
25 | Require the avro.php file in your source, and you should be good to go:
26 |
27 | /dev/null 2>&1 || { echo "docker is required to run this binary. Aborting." >&2; exit 1; }
6 |
7 | USER=${USER:-$( id -un )}
8 | GROUP=${GROUP:-$( id -gn )}
9 | COMPOSER_HOME=${COMPOSER_HOME:-${HOME}/.composer}
10 | PHP_VERSION=${PHP_VERSION:-8.1}
11 | DOCKER_OPTS=${DOCKER_OPTS:-'-it'}
12 |
13 | exec docker run ${DOCKER_OPTS} --rm \
14 | -u $( id -u ${USER} ):$( id -g ${USER} ) \
15 | -v "${PWD}":"${PWD}" \
16 | -v "${COMPOSER_HOME}":/tmp/composer \
17 | -w ${PWD} \
18 | -e PHP_IDE_CONFIG="serverName=avro-php" \
19 | -e COMPOSER_HOME="/tmp/composer" \
20 | --net=host --sig-proxy=true --pid=host \
21 | --entrypoint="php" \
22 | avro-php:${PHP_VERSION} "${@}"
23 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flix-tech/avro-php",
3 | "description": "Avro schema encoder/decoder. Fork of rg/avro-php",
4 | "license": "Apache-2.0",
5 | "require": {
6 | "php": "^8.1"
7 | },
8 | "require-dev": {
9 | "phpunit/phpunit": "^10.5"
10 | },
11 | "suggest": {
12 | "ext-gmp": "Large integer support for 32-bit platforms.",
13 | "ext-zlib": "Zlib support for Deflate codec.",
14 | "ext-snappy": "Snappy support for Snappy codec (mainly Google)."
15 | },
16 | "autoload": {
17 | "classmap": ["lib/"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/write_read.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | 1392, 'member_name' => 'Jose');
33 | $maria = array('member_id' => 1642, 'member_name' => 'Maria');
34 | $data = array($jose, $maria);
35 |
36 | $file_name = 'data.avr';
37 | // Open $file_name for writing, using the given writer's schema
38 | $data_writer = AvroDataIO::open_file($file_name, 'w', $writers_schema_json);
39 |
40 | // Write each datum to the file
41 | foreach ($data as $datum)
42 | $data_writer->append($datum);
43 | // Tidy up
44 | $data_writer->close();
45 |
46 | // Open $file_name (by default for reading) using the writer's schema
47 | // included in the file
48 | $data_reader = AvroDataIO::open_file($file_name);
49 | echo "from file:\n";
50 | // Read each datum
51 | foreach ($data_reader->data() as $datum)
52 | echo var_export($datum, true) . "\n";
53 | $data_reader->close();
54 |
55 | // Create a data string
56 | // Create a string io object.
57 | $io = new AvroStringIO();
58 | // Create a datum writer object
59 | $writers_schema = AvroSchema::parse($writers_schema_json);
60 | $writer = new AvroIODatumWriter($writers_schema);
61 | $data_writer = new AvroDataIOWriter($io, $writer, $writers_schema);
62 | foreach ($data as $datum)
63 | $data_writer->append($datum);
64 | $data_writer->close();
65 |
66 | $binary_string = $io->string();
67 |
68 | // Load the string data string
69 | $read_io = new AvroStringIO($binary_string);
70 | $data_reader = new AvroDataIOReader($read_io, new AvroIODatumReader());
71 | echo "from binary string:\n";
72 | foreach ($data_reader->data() as $datum)
73 | echo var_export($datum, true) . "\n";
74 |
75 | /** Output
76 | from file:
77 | array (
78 | 'member_id' => 1392,
79 | 'member_name' => 'Jose',
80 | )
81 | array (
82 | 'member_id' => 1642,
83 | 'member_name' => 'Maria',
84 | )
85 | from binary string:
86 | array (
87 | 'member_id' => 1392,
88 | 'member_name' => 'Jose',
89 | )
90 | array (
91 | 'member_id' => 1642,
92 | 'member_name' => 'Maria',
93 | )
94 | */
95 |
--------------------------------------------------------------------------------
/lib/avro.php:
--------------------------------------------------------------------------------
1 | io = $io;
250 | $this->decoder = new AvroIOBinaryDecoder($this->io);
251 | $this->datum_reader = $datum_reader;
252 | $this->read_header();
253 |
254 | $codec = AvroUtil::array_value($this->metadata,
255 | AvroDataIO::METADATA_CODEC_ATTR);
256 | if ($codec && !AvroDataIO::is_valid_codec($codec))
257 | throw new AvroDataIOException(sprintf('Uknown codec: %s', $codec));
258 | $this->codec = $codec;
259 |
260 | $this->block_count = 0;
261 | // FIXME: Seems unsanitary to set writers_schema here.
262 | // Can't constructor take it as an argument?
263 | $this->datum_reader->set_writers_schema(
264 | AvroSchema::parse($this->metadata[AvroDataIO::METADATA_SCHEMA_ATTR]));
265 | }
266 |
267 | /**
268 | * Reads header of object container
269 | * @throws AvroDataIOException if the file is not an Avro data file.
270 | */
271 | private function read_header()
272 | {
273 | $this->seek(0, AvroIO::SEEK_SET);
274 |
275 | $magic = $this->read(AvroDataIO::magic_size());
276 |
277 | if (strlen($magic) < AvroDataIO::magic_size())
278 | throw new AvroDataIOException(
279 | 'Not an Avro data file: shorter than the Avro magic block');
280 |
281 | if (AvroDataIO::magic() != $magic)
282 | throw new AvroDataIOException(
283 | sprintf('Not an Avro data file: %s does not match %s',
284 | $magic, AvroDataIO::magic()));
285 |
286 | $this->metadata = $this->datum_reader->read_data(AvroDataIO::metadata_schema(),
287 | AvroDataIO::metadata_schema(),
288 | $this->decoder);
289 | $this->sync_marker = $this->read(AvroDataIO::SYNC_SIZE);
290 | }
291 |
292 | /**
293 | * @return array of data from object container.
294 | * @throws AvroDataIOException
295 | * @throws AvroIOException
296 | */
297 | public function data()
298 | {
299 | $data = array();
300 | while (true)
301 | {
302 | if (0 == $this->block_count)
303 | {
304 | if ($this->is_eof())
305 | break;
306 |
307 | if ($this->skip_sync())
308 | if ($this->is_eof())
309 | break;
310 |
311 | $decoder = $this->apply_codec($this->decoder, $this->codec);
312 | }
313 | $data[] = $this->datum_reader->read($decoder);
314 | $this->block_count -= 1;
315 | }
316 | return $data;
317 | }
318 |
319 | /**
320 | * @throws AvroDataIOException
321 | * @throws AvroIOException
322 | */
323 | public function data_iterator()
324 | {
325 | while (true)
326 | {
327 | if (0 == $this->block_count)
328 | {
329 | if ($this->is_eof())
330 | break;
331 |
332 | if ($this->skip_sync())
333 | if ($this->is_eof())
334 | break;
335 |
336 | $decoder = $this->apply_codec($this->decoder, $this->codec);
337 | }
338 | yield $this->datum_reader->read($decoder);
339 | $this->block_count -= 1;
340 | }
341 | }
342 |
343 | /**
344 | * @param AvroIOBinaryDecoder $decoder
345 | * @param string $codec
346 | * @return AvroIOBinaryDecoder
347 | * @throws AvroDataIOException
348 | * @throws AvroIOException
349 | */
350 | protected function apply_codec($decoder, $codec)
351 | {
352 | $length = $this->read_block_header();
353 | if ($codec == AvroDataIO::DEFLATE_CODEC) {
354 | if (!function_exists('gzinflate')) {
355 | throw new AvroDataIOException('"gzinflate" function not available, "zlib" extension required.');
356 | }
357 | $compressed = $decoder->read($length);
358 | $datum = gzinflate($compressed);
359 | $decoder = new AvroIOBinaryDecoder(new AvroStringIO($datum));
360 | } elseif ($codec == AvroDataIO::SNAPPY_CODEC) {
361 | if (!function_exists('snappy_uncompress')) {
362 | throw new AvroDataIOException('"snappy_uncompress" function not available, "snappy" extension required.');
363 | }
364 | $compressed = $decoder->read($length-4);
365 | $datum = snappy_uncompress($compressed);
366 | $crc32 = unpack('N', $decoder->read(4));
367 | if ($crc32[1] != crc32($datum)) {
368 | throw new AvroDataIOException('Invalid CRC32 checksum.');
369 | }
370 | $decoder = new AvroIOBinaryDecoder(new AvroStringIO($datum));
371 | }
372 | return $decoder;
373 | }
374 |
375 | /**
376 | * Closes this writer (and its AvroIO object.)
377 | * @uses AvroIO::close()
378 | */
379 | public function close() { return $this->io->close(); }
380 |
381 | /**
382 | * @uses AvroIO::seek()
383 | * @param $offset
384 | * @param $whence
385 | * @return bool
386 | * @throws AvroNotImplementedException
387 | */
388 | private function seek($offset, $whence)
389 | {
390 | return $this->io->seek($offset, $whence);
391 | }
392 |
393 | /**
394 | * @uses AvroIO::read()
395 | * @param $len
396 | * @return string
397 | * @throws AvroNotImplementedException
398 | */
399 | private function read($len) { return $this->io->read($len); }
400 |
401 | /**
402 | * @uses AvroIO::is_eof()
403 | */
404 | private function is_eof() { return $this->io->is_eof(); }
405 |
406 | /**
407 | * @return bool
408 | */
409 | private function skip_sync()
410 | {
411 | $proposed_sync_marker = $this->read(AvroDataIO::SYNC_SIZE);
412 | if ($proposed_sync_marker != $this->sync_marker)
413 | {
414 | $this->seek(-AvroDataIO::SYNC_SIZE, AvroIO::SEEK_CUR);
415 | return false;
416 | }
417 | return true;
418 | }
419 |
420 | /**
421 | * Reads the block header (which includes the count of items in the block
422 | * and the length in bytes of the block)
423 | * @return int length in bytes of the block.
424 | */
425 | private function read_block_header()
426 | {
427 | $this->block_count = $this->decoder->read_long();
428 | return $this->decoder->read_long();
429 | }
430 |
431 | }
432 |
433 | /**
434 | * Writes Avro data to an AvroIO source using an AvroSchema
435 | * @package Avro
436 | */
437 | class AvroDataIOWriter
438 | {
439 | /**
440 | * @return string a new, unique sync marker.
441 | */
442 | private static function generate_sync_marker()
443 | {
444 | // From http://php.net/manual/en/function.mt-rand.php comments
445 | return pack('S8',
446 | mt_rand(0, 0xffff), mt_rand(0, 0xffff),
447 | mt_rand(0, 0xffff),
448 | mt_rand(0, 0xffff) | 0x4000,
449 | mt_rand(0, 0xffff) | 0x8000,
450 | mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
451 | }
452 |
453 | /**
454 | * @var AvroIO object container where data is written
455 | */
456 | private $io;
457 |
458 | /**
459 | * @var AvroIOBinaryEncoder encoder for object container
460 | */
461 | private $encoder;
462 |
463 | /**
464 | * @var AvroDatumWriter
465 | */
466 | private $datum_writer;
467 |
468 | /**
469 | * @var AvroStringIO buffer for writing
470 | */
471 | private $buffer;
472 |
473 | /**
474 | * @var AvroIOBinaryEncoder encoder for buffer
475 | */
476 | private $buffer_encoder; // AvroIOBinaryEncoder
477 |
478 | /**
479 | * @var int count of items written to block
480 | */
481 | private $block_count;
482 |
483 | /**
484 | * @var array map of object container metadata
485 | */
486 | private $metadata;
487 |
488 | /**
489 | * @var string compression codec
490 | */
491 | private $codec;
492 |
493 | private $sync_marker;
494 |
495 | /**
496 | * @param AvroIO $io
497 | * @param AvroIODatumWriter $datum_writer
498 | * @param AvroSchema $writers_schema
499 | * @param string $codec
500 | * @throws AvroDataIOException
501 | */
502 | public function __construct($io, $datum_writer, $writers_schema=null, $codec=AvroDataIO::NULL_CODEC)
503 | {
504 | if (!($io instanceof AvroIO))
505 | throw new AvroDataIOException('io must be instance of AvroIO');
506 |
507 | $this->io = $io;
508 | $this->encoder = new AvroIOBinaryEncoder($this->io);
509 | $this->datum_writer = $datum_writer;
510 | $this->buffer = new AvroStringIO();
511 | $this->buffer_encoder = new AvroIOBinaryEncoder($this->buffer);
512 | $this->block_count = 0;
513 | $this->metadata = array();
514 |
515 | if ($writers_schema)
516 | {
517 | if (!AvroDataIO::is_valid_codec($codec))
518 | throw new AvroDataIOException(
519 | sprintf('codec %s is not supported', $codec));
520 |
521 | $this->sync_marker = self::generate_sync_marker();
522 | $this->metadata[AvroDataIO::METADATA_CODEC_ATTR] = $this->codec = $codec;
523 | $this->metadata[AvroDataIO::METADATA_SCHEMA_ATTR] = strval($writers_schema);
524 | $this->write_header();
525 | }
526 | else
527 | {
528 | $dfr = new AvroDataIOReader($this->io, new AvroIODatumReader());
529 | $this->sync_marker = $dfr->sync_marker;
530 | $this->metadata[AvroDataIO::METADATA_CODEC_ATTR] = $this->codec
531 | = $dfr->metadata[AvroDataIO::METADATA_CODEC_ATTR];
532 |
533 | $schema_from_file = $dfr->metadata[AvroDataIO::METADATA_SCHEMA_ATTR];
534 | $this->metadata[AvroDataIO::METADATA_SCHEMA_ATTR] = $schema_from_file;
535 | $this->datum_writer->writers_schema = AvroSchema::parse($schema_from_file);
536 | $this->seek(0, SEEK_END);
537 | }
538 | }
539 |
540 | /**
541 | * @param mixed $datum
542 | */
543 | public function append($datum)
544 | {
545 | $this->datum_writer->write($datum, $this->buffer_encoder);
546 | $this->block_count++;
547 |
548 | if ($this->buffer->length() >= AvroDataIO::SYNC_INTERVAL)
549 | $this->write_block();
550 | }
551 |
552 | /**
553 | * Flushes buffer to AvroIO object container and closes it.
554 | * @return mixed value of $io->close()
555 | * @see AvroIO::close()
556 | */
557 | public function close()
558 | {
559 | $this->flush();
560 | return $this->io->close();
561 | }
562 |
563 | /**
564 | * Flushes biffer to AvroIO object container.
565 | * @return mixed value of $io->flush()
566 | * @see AvroIO::flush()
567 | */
568 | private function flush()
569 | {
570 | $this->write_block();
571 | return $this->io->flush();
572 | }
573 |
574 | /**
575 | * Writes a block of data to the AvroIO object container.
576 | */
577 | private function write_block()
578 | {
579 | if ($this->block_count > 0)
580 | {
581 | $this->encoder->write_long($this->block_count);
582 | $to_write = strval($this->buffer);
583 |
584 | if ($this->codec == AvroDataIO::DEFLATE_CODEC) {
585 | if (!function_exists('gzinflate')) {
586 | throw new AvroDataIOException('"gzinflate" function not available, "zlib" extension required.');
587 | }
588 | $to_write = gzdeflate($to_write);
589 | } elseif ($this->codec == AvroDataIO::SNAPPY_CODEC) {
590 | if (!function_exists('snappy_compress')) {
591 | throw new AvroDataIOException('"snappy_compress" function not available, "snappy" extension required.');
592 | }
593 | $crc32 = pack('N', crc32($to_write));
594 | $to_write = snappy_compress($to_write) . $crc32;
595 | }
596 |
597 | $this->encoder->write_long(strlen($to_write));
598 | $this->write($to_write);
599 | $this->write($this->sync_marker);
600 | $this->buffer->truncate();
601 | $this->block_count = 0;
602 | }
603 | }
604 |
605 | /**
606 | * Writes the header of the AvroIO object container
607 | */
608 | private function write_header()
609 | {
610 | $this->write(AvroDataIO::magic());
611 | $this->datum_writer->write_data(AvroDataIO::metadata_schema(),
612 | $this->metadata, $this->encoder);
613 | $this->write($this->sync_marker);
614 | }
615 |
616 | /**
617 | * @param string $bytes
618 | * @uses AvroIO::write()
619 | * @return int
620 | */
621 | private function write($bytes) { return $this->io->write($bytes); }
622 |
623 | /**
624 | * @param int $offset
625 | * @param int $whence
626 | * @uses AvroIO::seek()
627 | * @return bool
628 | */
629 | private function seek($offset, $whence)
630 | {
631 | return $this->io->seek($offset, $whence);
632 | }
633 | }
634 |
--------------------------------------------------------------------------------
/lib/avro/debug.php:
--------------------------------------------------------------------------------
1 | = $debug_level);
53 | }
54 |
55 | /**
56 | * @param string $format format string for the given arguments. Passed as is
57 | * to vprintf
.
58 | * @param array $args array of arguments to pass to vsprinf.
59 | * @param int $debug_level debug level at which to print this statement
60 | * @return boolean true
61 | */
62 | static function debug($format, $args, $debug_level=self::DEBUG1)
63 | {
64 | if (self::is_debug($debug_level))
65 | vprintf($format . "\n", $args);
66 | return true;
67 | }
68 |
69 | /**
70 | * @param string $str
71 | * @return string[] array of hex representation of each byte of $str
72 | */
73 | static function hex_array($str) { return self::bytes_array($str); }
74 |
75 | /**
76 | * @param string $str
77 | * @param string $joiner string used to join
78 | * @return string hex-represented bytes of each byte of $str
79 | joined by $joiner
80 | */
81 | static function hex_string($str, $joiner=' ')
82 | {
83 | return join($joiner, self::hex_array($str));
84 | }
85 |
86 | /**
87 | * @param string $str
88 | * @param string $format format to represent bytes
89 | * @return string[] array of each byte of $str formatted using $format
90 | */
91 | static function bytes_array($str, $format='x%02x')
92 | {
93 | $x = array();
94 | foreach (str_split($str) as $b)
95 | $x []= sprintf($format, ord($b));
96 | return $x;
97 | }
98 |
99 | /**
100 | * @param string $str
101 | * @return string[] array of bytes of $str represented in decimal format ('%3d')
102 | */
103 | static function dec_array($str) { return self::bytes_array($str, '%3d'); }
104 |
105 | /**
106 | * @param string $str
107 | * @param string $joiner string to join bytes of $str
108 | * @return string of bytes of $str represented in decimal format
109 | * @uses dec_array()
110 | */
111 | static function dec_string($str, $joiner = ' ')
112 | {
113 | return join($joiner, self::dec_array($str));
114 | }
115 |
116 | /**
117 | * @param string $str
118 | * @param string $format one of 'ctrl', 'hex', or 'dec' for control,
119 | * hexadecimal, or decimal format for bytes.
120 | * - ctrl: ASCII control characters represented as text.
121 | * For example, the null byte is represented as 'NUL'.
122 | * Visible ASCII characters represent themselves, and
123 | * others are represented as a decimal ('%03d')
124 | * - hex: bytes represented in hexadecimal ('%02X')
125 | * - dec: bytes represented in decimal ('%03d')
126 | * @return string[] array of bytes represented in the given format.
127 | * @throws AvroException
128 | */
129 | static function ascii_array($str, $format='ctrl')
130 | {
131 | if (!in_array($format, array('ctrl', 'hex', 'dec')))
132 | throw new AvroException('Unrecognized format specifier');
133 |
134 | $ctrl_chars = array('NUL', 'SOH', 'STX', 'ETX', 'EOT', 'ENQ', 'ACK', 'BEL',
135 | 'BS', 'HT', 'LF', 'VT', 'FF', 'CR', 'SO', 'SI',
136 | 'DLE', 'DC1', 'DC2', 'DC3', 'DC4', 'NAK', 'SYN', 'ETB',
137 | 'CAN', 'EM', 'SUB', 'ESC', 'FS', 'GS', 'RS', 'US');
138 | $x = array();
139 | foreach (str_split($str) as $b)
140 | {
141 | $db = ord($b);
142 | if ($db < 32)
143 | {
144 | switch ($format)
145 | {
146 | case 'ctrl':
147 | $x []= str_pad($ctrl_chars[$db], 3, ' ', STR_PAD_LEFT);
148 | break;
149 | case 'hex':
150 | $x []= sprintf("x%02X", $db);
151 | break;
152 | case 'dec':
153 | $x []= str_pad($db, 3, '0', STR_PAD_LEFT);
154 | break;
155 | }
156 | }
157 | else if ($db < 127)
158 | $x []= " $b";
159 | else if ($db == 127)
160 | {
161 | switch ($format)
162 | {
163 | case 'ctrl':
164 | $x []= 'DEL';
165 | break;
166 | case 'hex':
167 | $x []= sprintf("x%02X", $db);
168 | break;
169 | case 'dec':
170 | $x []= str_pad($db, 3, '0', STR_PAD_LEFT);
171 | break;
172 | }
173 | }
174 | else
175 | if ('hex' == $format)
176 | $x []= sprintf("x%02X", $db);
177 | else
178 | $x []= str_pad($db, 3, '0', STR_PAD_LEFT);
179 | }
180 | return $x;
181 | }
182 |
183 | /**
184 | * @param string $str
185 | * @param string $format one of 'ctrl', 'hex', or 'dec'.
186 | * See {@link self::ascii_array()} for more description
187 | * @param string $joiner
188 | * @return string of bytes joined by $joiner
189 | * @uses ascii_array()
190 | */
191 | static function ascii_string($str, $format='ctrl', $joiner = ' ')
192 | {
193 | return join($joiner, self::ascii_array($str, $format));
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/lib/avro/gmp.php:
--------------------------------------------------------------------------------
1 | gmp_sign($g))
146 | $g = self::gmp_twos_complement($g);
147 |
148 | $m = gmp_mul($g, gmp_pow(self::gmp_2(), $shift));
149 | $m = gmp_and($m, self::gmp_0xfs());
150 | if (gmp_testbit($m, 63))
151 | $m = gmp_neg(gmp_add(gmp_and(gmp_com($m), self::gmp_0xfs()),
152 | self::gmp_1()));
153 | return $m;
154 | }
155 |
156 | /**
157 | * Arithmetic right shift
158 | * @param resource|int|string $g
159 | * @param int $shift number of bits to shift right
160 | * @return resource $g shifted right $shift bits
161 | */
162 | static function shift_right($g, $shift)
163 | {
164 | if (0 == $shift)
165 | return $g;
166 |
167 | if (0 <= gmp_sign($g))
168 | $m = gmp_div($g, gmp_pow(self::gmp_2(), $shift));
169 | else // negative
170 | {
171 | $g = gmp_and($g, self::gmp_0xfs());
172 | $m = gmp_div($g, gmp_pow(self::gmp_2(), $shift));
173 | $m = gmp_and($m, self::gmp_0xfs());
174 | for ($i = 63; $i >= (63 - $shift); $i--)
175 | gmp_setbit($m, $i);
176 |
177 | $m = gmp_neg(gmp_add(gmp_and(gmp_com($m), self::gmp_0xfs()),
178 | self::gmp_1()));
179 | }
180 |
181 | return $m;
182 | }
183 |
184 | /**
185 | * @param int|string $n integer (or string representation of integer) to encode
186 | * @return string $bytes of the long $n encoded per the Avro spec
187 | */
188 | static function encode_long($n)
189 | {
190 | $g = gmp_init($n);
191 | $g = gmp_xor(self::shift_left($g, 1),
192 | self::shift_right($g, 63));
193 | $bytes = '';
194 | while (0 != gmp_cmp(self::gmp_0(), gmp_and($g, self::gmp_n0x7f())))
195 | {
196 | $bytes .= chr(gmp_intval(gmp_and($g, self::gmp_0x7f())) | 0x80);
197 | $g = self::shift_right($g, 7);
198 | }
199 | $bytes .= chr(gmp_intval($g));
200 | return $bytes;
201 | }
202 |
203 | /**
204 | * @param int[] $bytes array of ascii codes of bytes to decode
205 | * @return string represenation of decoded long.
206 | */
207 | static function decode_long_from_array($bytes)
208 | {
209 | $b = array_shift($bytes);
210 | $g = gmp_init($b & 0x7f);
211 | $shift = 7;
212 | while (0 != ($b & 0x80))
213 | {
214 | $b = array_shift($bytes);
215 | $g = gmp_or($g, self::shift_left(($b & 0x7f), $shift));
216 | $shift += 7;
217 | }
218 | $val = gmp_xor(self::shift_right($g, 1), gmp_neg(gmp_and($g, 1)));
219 | return gmp_strval($val);
220 | }
221 |
222 | }
223 |
--------------------------------------------------------------------------------
/lib/avro/io.php:
--------------------------------------------------------------------------------
1 | not like eof in C or feof in PHP:
126 | * it returns TRUE if the *next* read would be end of file,
127 | * rather than if the *most recent* read read end of file.
128 | * @return bool true if at the end of file, and false otherwise
129 | * @throws AvroNotImplementedException
130 | */
131 | public function is_eof()
132 | {
133 | throw new AvroNotImplementedException('Not implemented');
134 | }
135 |
136 | /**
137 | * Closes this AvroIO instance.
138 | */
139 | public function close()
140 | {
141 | throw new AvroNotImplementedException('Not implemented');
142 | }
143 |
144 | }
145 |
146 | /**
147 | * AvroIO wrapper for string access
148 | * @package Avro
149 | */
150 | class AvroStringIO extends AvroIO
151 | {
152 | /**
153 | * @var string
154 | */
155 | private $string_buffer;
156 | /**
157 | * @var int current position in string
158 | */
159 | private $current_index;
160 | /**
161 | * @var boolean whether or not the string is closed.
162 | */
163 | private $is_closed;
164 |
165 | /**
166 | * @param string $str initial value of AvroStringIO buffer. Regardless
167 | * of the initial value, the pointer is set to the
168 | * beginning of the buffer.
169 | * @throws AvroIOException if a non-string value is passed as $str
170 | */
171 | public function __construct($str = '')
172 | {
173 | $this->is_closed = false;
174 | $this->string_buffer = '';
175 | $this->current_index = 0;
176 |
177 | if (is_string($str))
178 | $this->string_buffer .= $str;
179 | else
180 | throw new AvroIOException(
181 | sprintf('constructor argument must be a string: %s', gettype($str)));
182 | }
183 |
184 | /**
185 | * Append bytes to this buffer.
186 | * (Nothing more is needed to support Avro.)
187 | * @param string $arg bytes to write
188 | * @return int count of bytes written.
189 | * @throws AvroIOException if $args is not a string value.
190 | */
191 | public function write($arg)
192 | {
193 | $this->check_closed();
194 | if (is_string($arg))
195 | return $this->append_str($arg);
196 | throw new AvroIOException(
197 | sprintf('write argument must be a string: (%s) %s',
198 | gettype($arg), var_export($arg, true)));
199 | }
200 |
201 | /**
202 | * @param int $len
203 | * @return string bytes read from buffer
204 | * @throws AvroIOException
205 | * @todo test for fencepost errors wrt updating current_index
206 | */
207 | public function read($len)
208 | {
209 | $this->check_closed();
210 | $read='';
211 | for($i=$this->current_index; $i<($this->current_index+$len); $i++) {
212 | if (!isset($this->string_buffer[$i])) continue;
213 | $read .= $this->string_buffer[$i];
214 | }
215 | if (strlen($read) < $len)
216 | $this->current_index = $this->length();
217 | else
218 | $this->current_index += $len;
219 | return $read;
220 | }
221 |
222 | /**
223 | * @param int $offset
224 | * @param int $whence
225 | * @return bool true if successful
226 | * @throws AvroIOException if the seek failed.
227 | */
228 | public function seek($offset, $whence=self::SEEK_SET)
229 | {
230 | if (!is_int($offset))
231 | throw new AvroIOException('Seek offset must be an integer.');
232 | // Prevent seeking before BOF
233 | switch ($whence)
234 | {
235 | case self::SEEK_SET:
236 | if (0 > $offset)
237 | throw new AvroIOException('Cannot seek before beginning of file.');
238 | $this->current_index = $offset;
239 | break;
240 | case self::SEEK_CUR:
241 | if (0 > $this->current_index + $whence)
242 | throw new AvroIOException('Cannot seek before beginning of file.');
243 | $this->current_index += $offset;
244 | break;
245 | case self::SEEK_END:
246 | if (0 > $this->length() + $offset)
247 | throw new AvroIOException('Cannot seek before beginning of file.');
248 | $this->current_index = $this->length() + $offset;
249 | break;
250 | default:
251 | throw new AvroIOException(sprintf('Invalid seek whence %d', $whence));
252 | }
253 |
254 | return true;
255 | }
256 |
257 | /**
258 | * @return int
259 | * @see AvroIO::tell()
260 | */
261 | public function tell() { return $this->current_index; }
262 |
263 | /**
264 | * @return boolean
265 | * @see AvroIO::is_eof()
266 | */
267 | public function is_eof()
268 | {
269 | return ($this->current_index >= $this->length());
270 | }
271 |
272 | /**
273 | * No-op provided for compatibility with AvroIO interface.
274 | * @return boolean true
275 | */
276 | public function flush() { return true; }
277 |
278 | /**
279 | * Marks this buffer as closed.
280 | * @return boolean true
281 | */
282 | public function close()
283 | {
284 | $this->check_closed();
285 | $this->is_closed = true;
286 | return true;
287 | }
288 |
289 | /**
290 | * @throws AvroIOException if the buffer is closed.
291 | */
292 | private function check_closed()
293 | {
294 | if ($this->is_closed())
295 | throw new AvroIOException('Buffer is closed');
296 | }
297 |
298 | /**
299 | * Appends bytes to this buffer.
300 | * @param string $str
301 | * @return integer count of bytes written.
302 | */
303 | private function append_str($str)
304 | {
305 | $this->check_closed();
306 | $this->string_buffer .= $str;
307 | $len = strlen($str);
308 | $this->current_index += $len;
309 | return $len;
310 | }
311 |
312 | /**
313 | * Truncates the truncate buffer to 0 bytes and returns the pointer
314 | * to the beginning of the buffer.
315 | * @return boolean true
316 | */
317 | public function truncate()
318 | {
319 | $this->check_closed();
320 | $this->string_buffer = '';
321 | $this->current_index = 0;
322 | return true;
323 | }
324 |
325 | /**
326 | * @return int count of bytes in the buffer
327 | * @internal Could probably memoize length for performance, but
328 | * no need do this yet.
329 | */
330 | public function length() { return strlen($this->string_buffer); }
331 |
332 | /**
333 | * @return string
334 | */
335 | public function __toString() { return $this->string_buffer; }
336 |
337 |
338 | /**
339 | * @return string
340 | * @uses self::__toString()
341 | */
342 | public function string() { return $this->__toString(); }
343 |
344 | /**
345 | * @return boolean true if this buffer is closed and false
346 | * otherwise.
347 | */
348 | public function is_closed() { return $this->is_closed; }
349 | }
350 |
351 | /**
352 | * AvroIO wrapper for PHP file access functions
353 | * @package Avro
354 | */
355 | class AvroFile extends AvroIO
356 | {
357 | /**
358 | * @var string fopen read mode value. Used internally.
359 | */
360 | const FOPEN_READ_MODE = 'rb';
361 |
362 | /**
363 | * @var string fopen write mode value. Used internally.
364 | */
365 | const FOPEN_WRITE_MODE = 'wb';
366 |
367 | /**
368 | * @var string
369 | */
370 | private $file_path;
371 |
372 | /**
373 | * @var resource file handle for AvroFile instance
374 | */
375 | private $file_handle;
376 |
377 | /**
378 | * AvroFile constructor.
379 | * @param $file_path
380 | * @param string $mode
381 | * @throws AvroIOException
382 | */
383 | public function __construct($file_path, $mode = self::READ_MODE)
384 | {
385 | /**
386 | * XXX: should we check for file existence (in case of reading)
387 | * or anything else about the provided file_path argument?
388 | */
389 | $this->file_path = $file_path;
390 | switch ($mode)
391 | {
392 | case self::WRITE_MODE:
393 | $this->file_handle = fopen($this->file_path, self::FOPEN_WRITE_MODE);
394 | if (false == $this->file_handle)
395 | throw new AvroIOException('Could not open file for writing');
396 | break;
397 | case self::READ_MODE:
398 | $this->file_handle = fopen($this->file_path, self::FOPEN_READ_MODE);
399 | if (false == $this->file_handle)
400 | throw new AvroIOException('Could not open file for reading');
401 | break;
402 | default:
403 | throw new AvroIOException(
404 | sprintf("Only modes '%s' and '%s' allowed. You provided '%s'.",
405 | self::READ_MODE, self::WRITE_MODE, $mode));
406 | }
407 | }
408 |
409 | /**
410 | * @param string $str
411 | * @return int count of bytes written
412 | * @throws AvroIOException if write failed.
413 | */
414 | public function write($str)
415 | {
416 | $len = fwrite($this->file_handle, $str);
417 | if (false === $len)
418 | throw new AvroIOException(sprintf('Could not write to file'));
419 | return $len;
420 | }
421 |
422 | /**
423 | * @param int $len count of bytes to read.
424 | * @return string bytes read
425 | * @throws AvroIOException if length value is negative or if the read failed
426 | */
427 | public function read($len)
428 | {
429 | if (0 > $len)
430 | throw new AvroIOException(
431 | sprintf("Invalid length value passed to read: %d", $len));
432 |
433 | if (0 == $len)
434 | return '';
435 |
436 | $bytes = fread($this->file_handle, $len);
437 | if (false === $bytes)
438 | throw new AvroIOException('Could not read from file');
439 | return $bytes;
440 | }
441 |
442 | /**
443 | * @return int current position within the file
444 | * @throws AvroIOException
445 | */
446 | public function tell()
447 | {
448 | $position = ftell($this->file_handle);
449 | if (false === $position)
450 | throw new AvroIOException('Could not execute tell on reader');
451 | return $position;
452 | }
453 |
454 | /**
455 | * @param int $offset
456 | * @param int $whence
457 | * @return boolean true upon success
458 | * @throws AvroIOException if seek failed.
459 | * @see AvroIO::seek()
460 | */
461 | public function seek($offset, $whence = SEEK_SET)
462 | {
463 | $res = fseek($this->file_handle, $offset, $whence);
464 | // Note: does not catch seeking beyond end of file
465 | if (-1 === $res)
466 | throw new AvroIOException(
467 | sprintf("Could not execute seek (offset = %d, whence = %d)",
468 | $offset, $whence));
469 | return true;
470 | }
471 |
472 | /**
473 | * Closes the file.
474 | * @return boolean true if successful.
475 | * @throws AvroIOException if there was an error closing the file.
476 | */
477 | public function close()
478 | {
479 | $res = fclose($this->file_handle);
480 | if (false === $res)
481 | throw new AvroIOException('Error closing file.');
482 | return $res;
483 | }
484 |
485 | /**
486 | * @return boolean true if the pointer is at the end of the file,
487 | * and false otherwise.
488 | * @see AvroIO::is_eof() as behavior differs from feof()
489 | */
490 | public function is_eof()
491 | {
492 | $this->read(1);
493 | if (feof($this->file_handle))
494 | return true;
495 | $this->seek(-1, self::SEEK_CUR);
496 | return false;
497 | }
498 |
499 | /**
500 | * @return boolean true if the flush was successful.
501 | * @throws AvroIOException if there was an error flushing the file.
502 | */
503 | public function flush()
504 | {
505 | $res = fflush($this->file_handle);
506 | if (false === $res)
507 | throw new AvroIOException('Could not flush file.');
508 | return true;
509 | }
510 |
511 | }
512 |
--------------------------------------------------------------------------------
/lib/avro/protocol.php:
--------------------------------------------------------------------------------
1 | real_parse(json_decode($json, true));
48 | return $protocol;
49 | }
50 |
51 | /**
52 | * @param $avro
53 | * @throws AvroSchemaParseException
54 | */
55 | function real_parse($avro) {
56 | $this->protocol = $avro["protocol"];
57 | $this->namespace = $avro["namespace"];
58 | $this->schemata = new AvroNamedSchemata();
59 | $this->name = $avro["protocol"];
60 |
61 | if (!is_null($avro["types"])) {
62 | $types = AvroSchema::real_parse($avro["types"], $this->namespace, $this->schemata);
63 | }
64 |
65 | if (!is_null($avro["messages"])) {
66 | foreach ($avro["messages"] as $messageName => $messageAvro) {
67 | $message = new AvroProtocolMessage($messageName, $messageAvro, $this);
68 | $this->messages[$messageName] = $message;
69 | }
70 | }
71 | }
72 | }
73 |
74 | /**
75 | * Class AvroProtocolMessage
76 | */
77 | class AvroProtocolMessage
78 | {
79 | /**
80 | * @var AvroRecordSchema $request
81 | */
82 |
83 | public $request;
84 |
85 | public $response;
86 |
87 | private $name;
88 |
89 | /**
90 | * AvroProtocolMessage constructor.
91 | * @param $name
92 | * @param $avro
93 | * @param $protocol
94 | */
95 | public function __construct($name, $avro, $protocol)
96 | {
97 | $this->name = $name;
98 | $this->request = new AvroRecordSchema(new AvroName($name, null, $protocol->namespace), null, $avro['request'], $protocol->schemata, AvroSchema::REQUEST_SCHEMA);
99 |
100 | if (array_key_exists('response', $avro)) {
101 | $this->response = $protocol->schemata->schema_by_name(new AvroName($avro['response'], $protocol->namespace, $protocol->namespace));
102 | if ($this->response == null)
103 | $this->response = new AvroPrimitiveSchema($avro['response']);
104 | }
105 | }
106 | }
107 |
108 | /**
109 | * Class AvroProtocolParseException
110 | */
111 | class AvroProtocolParseException extends AvroException {};
112 |
--------------------------------------------------------------------------------
/lib/avro/util.php:
--------------------------------------------------------------------------------
1 | $v)
47 | {
48 | if ($i !== $k)
49 | return false;
50 | $i++;
51 | }
52 | return true;
53 | }
54 | return false;
55 | }
56 |
57 | /**
58 | * @param array $ary
59 | * @param string $key
60 | * @return mixed the value of $ary[$key] if it is set,
61 | * and null otherwise.
62 | */
63 | static function array_value($ary, $key)
64 | {
65 | return isset($ary[$key]) ? $ary[$key] : null;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 | lib
11 |
12 |
13 |
14 |
15 | test
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/DataFileTest.php:
--------------------------------------------------------------------------------
1 | data_files))
42 | $this->data_files = array();
43 | $data_file = "$data_file.".self::current_timestamp();
44 | $full = join(DIRECTORY_SEPARATOR, array(TEST_TEMP_DIR, $data_file));
45 | $this->data_files []= $full;
46 | return $full;
47 | }
48 |
49 | /**
50 | * @param $data_file
51 | */
52 | protected static function remove_data_file($data_file)
53 | {
54 | if (file_exists($data_file))
55 | unlink($data_file);
56 | }
57 |
58 | protected function remove_data_files()
59 | {
60 | if (self::REMOVE_DATA_FILES && $this->data_files)
61 | foreach ($this->data_files as $data_file)
62 | $this->remove_data_file($data_file);
63 | }
64 |
65 | protected function setUp(): void
66 | {
67 | if (!file_exists(TEST_TEMP_DIR))
68 | mkdir(TEST_TEMP_DIR);
69 | $this->remove_data_files();
70 | }
71 |
72 | protected function tearDown(): void
73 | {
74 | $this->remove_data_files();
75 | }
76 |
77 | public function provider()
78 | {
79 | $values = [];
80 | foreach (AvroDataIO::valid_codecs() as $codec) {
81 | $values[] = [$codec];
82 | }
83 | return $values;
84 | }
85 |
86 | /**
87 | * @dataProvider provider
88 | */
89 | public function test_write_read_nothing_round_trip($codec)
90 | {
91 | $data_file = $this->add_data_file(sprintf('data-wr-nothing-null-%s.avr', $codec));
92 | $writers_schema = '"null"';
93 | $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema, $codec);
94 | $dw->close();
95 |
96 | $dr = AvroDataIO::open_file($data_file);
97 | $data = $dr->data();
98 | $dr->close();
99 | $this->assertEmpty($data);
100 | }
101 |
102 | /**
103 | * @dataProvider provider
104 | */
105 | public function test_write_read_null_round_trip($codec)
106 | {
107 | $data_file = $this->add_data_file(sprintf('data-wr-null-%s.avr', $codec));
108 | $writers_schema = '"null"';
109 | $data = null;
110 | $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema, $codec);
111 | $dw->append($data);
112 | $dw->close();
113 |
114 | $dr = AvroDataIO::open_file($data_file);
115 | $read_data = $dr->data();
116 | $read_data = reset($read_data);
117 | $dr->close();
118 | $this->assertEquals($data, $read_data);
119 | }
120 |
121 | /**
122 | * @dataProvider provider
123 | */
124 | public function test_write_read_string_round_trip($codec)
125 | {
126 | $data_file = $this->add_data_file(sprintf('data-wr-str-%s.avr', $codec));
127 | $writers_schema = '"string"';
128 | $data = 'foo';
129 | $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema, $codec);
130 | $dw->append($data);
131 | $dw->close();
132 |
133 | $dr = AvroDataIO::open_file($data_file);
134 | $read_data = $dr->data();
135 | $read_data = reset($read_data);
136 | $dr->close();
137 | $this->assertEquals($data, $read_data);
138 | }
139 |
140 | /**
141 | * @dataProvider provider
142 | */
143 | public function test_write_read_round_trip($codec)
144 | {
145 | $data_file = $this->add_data_file(sprintf('data-wr-int-%s.avr', $codec));
146 | $writers_schema = '"int"';
147 | $data = 1;
148 |
149 | $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema, $codec);
150 | $dw->append(1);
151 | $dw->close();
152 |
153 | $dr = AvroDataIO::open_file($data_file);
154 | $read_data = $dr->data();
155 | $read_data = reset($read_data);
156 | $dr->close();
157 | $this->assertEquals($data, $read_data);
158 | }
159 |
160 | /**
161 | * @dataProvider provider
162 | */
163 | public function test_write_read_true_round_trip($codec)
164 | {
165 | $data_file = $this->add_data_file(sprintf('data-wr-true-%s.avr', $codec));
166 | $writers_schema = '"boolean"';
167 | $datum = true;
168 | $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema, $codec);
169 | $dw->append($datum);
170 | $dw->close();
171 |
172 | $dr = AvroDataIO::open_file($data_file);
173 | $read_data = $dr->data();
174 | $read_data = reset($read_data);
175 | $dr->close();
176 | $this->assertEquals($datum, $read_data);
177 | }
178 |
179 | /**
180 | * @dataProvider provider
181 | */
182 | public function test_write_read_false_round_trip($codec)
183 | {
184 | $data_file = $this->add_data_file(sprintf('data-wr-false-%s.avr', $codec));
185 | $writers_schema = '"boolean"';
186 | $datum = false;
187 | $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema, $codec);
188 | $dw->append($datum);
189 | $dw->close();
190 |
191 | $dr = AvroDataIO::open_file($data_file);
192 | $read_data = $dr->data();
193 | $read_data = reset($read_data);
194 | $dr->close();
195 | $this->assertEquals($datum, $read_data);
196 | }
197 |
198 | /**
199 | * @dataProvider provider
200 | */
201 | public function test_write_read_int_array_round_trip($codec)
202 | {
203 | $data_file = $this->add_data_file(sprintf('data-wr-int-ary-%s.avr', $codec));
204 | $writers_schema = '"int"';
205 | $data = array(10, 20, 30, 40, 50, 60, 70, 567, 89012345);
206 | $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema, $codec);
207 | foreach ($data as $datum)
208 | $dw->append($datum);
209 | $dw->close();
210 |
211 | $dr = AvroDataIO::open_file($data_file);
212 | $read_data = $dr->data();
213 | $dr->close();
214 | $this->assertEquals($data, $read_data,
215 | sprintf("in: %s\nout: %s",
216 | json_encode($data), json_encode($read_data)));
217 | }
218 |
219 | /**
220 | * @dataProvider provider
221 | */
222 | public function test_differing_schemas_with_primitives($codec)
223 | {
224 | $data_file = $this->add_data_file(sprintf('data-prim-%s.avr', $codec));
225 |
226 | $writer_schema = << 'john', 'age' => 25, 'verified' => true),
236 | array('username' => 'ryan', 'age' => 23, 'verified' => false),
237 | array('username' => 'bill', 'age' => 35));
238 | $dw = AvroDataIO::open_file($data_file, 'w', $writer_schema, $codec);
239 | foreach ($data as $datum)
240 | {
241 | $dw->append($datum);
242 | }
243 | $dw->close();
244 | $reader_schema = <<data() as $index => $record)
253 | {
254 | $this->assertEquals($data[$index]['username'], $record['username']);
255 | }
256 | }
257 |
258 | /**
259 | * @dataProvider provider
260 | */
261 | public function test_differing_schemas_with_complex_objects($codec)
262 | {
263 | $data_file = $this->add_data_file(sprintf('data-complex-%s.avr', $codec));
264 |
265 | $writers_schema = << "john",
286 | "something_fixed" => "foo",
287 | "something_enum" => "hello",
288 | "something_array" => array(1,2,3),
289 | "something_map" => array("a" => 1, "b" => 2),
290 | "something_record" => array("inner" => 2),
291 | "something_error" => array("code" => 403)),
292 | array("username" => "ryan",
293 | "something_fixed" => "bar",
294 | "something_enum" => "goodbye",
295 | "something_array" => array(1,2,3),
296 | "something_map" => array("a" => 2, "b" => 6),
297 | "something_record" => array("inner" => 1),
298 | "something_error" => array("code" => 401)));
299 | $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema, $codec);
300 | foreach ($data as $datum)
301 | $dw->append($datum);
302 | $dw->close();
303 |
304 | foreach (array('fixed', 'enum', 'record', 'error',
305 | 'array', 'map', 'union') as $s)
306 | {
307 | $readers_schema = json_decode($writers_schema, true);
308 | $dr = AvroDataIO::open_file($data_file, 'r', json_encode($readers_schema));
309 | foreach ($dr->data() as $idx => $obj)
310 | {
311 | foreach ($readers_schema['fields'] as $field)
312 | {
313 | $field_name = $field['name'];
314 | $this->assertEquals($data[$idx][$field_name], $obj[$field_name]);
315 | }
316 | }
317 | $dr->close();
318 |
319 | }
320 |
321 | }
322 |
323 | }
324 |
--------------------------------------------------------------------------------
/test/DatumIOTest.php:
--------------------------------------------------------------------------------
1 | write($datum, $encoder);
41 | $output = strval($written);
42 | $this->assertEquals($binary, $output,
43 | sprintf("expected: %s\n actual: %s",
44 | AvroDebug::ascii_string($binary, 'hex'),
45 | AvroDebug::ascii_string($output, 'hex')));
46 |
47 | $read = new AvroStringIO($binary);
48 | $decoder = new AvroIOBinaryDecoder($read);
49 | $reader = new AvroIODatumReader($schema);
50 | $read_datum = $reader->read($decoder);
51 | $this->assertEquals($datum, $read_datum);
52 | }
53 |
54 | /**
55 | * @dataProvider zigzag_unsigned_right_shift_provider
56 | */
57 | function test_zigzag_unsigned_right_shift(int $expected, int $n, int $x) {
58 | $this->assertEquals($expected, Zigzag::unsigned_right_shift($n, $x));
59 | }
60 |
61 | public static function zigzag_unsigned_right_shift_provider(): array {
62 | return [
63 | [4611686018427387902, -8, 2],
64 | [2, 8, 2],
65 | [144115188075855871, -2, 7],
66 | [1125899906842623, 144115188075855871, 7],
67 | [8796093022207, 1125899906842623, 7],
68 | [68719476735, 8796093022207, 7],
69 | [536870911, 68719476735, 7],
70 | [4194303, 536870911, 7],
71 | [32767, 4194303, 7],
72 | [255, 32767, 7],
73 | [1, 255, 7],
74 | [144115188059078656, -2147483648, 7],
75 | [1125899906711552, 144115188059078656, 7],
76 | [8796093021184, 1125899906711552, 7],
77 | [68719476728, 8796093021184, 7],
78 | [536870911, 68719476728, 7],
79 | [4194303, 536870911, 7],
80 | [32767, 4194303, 7],
81 | [255, 32767, 7],
82 | ];
83 | }
84 |
85 | /**
86 | * @return array
87 | */
88 | function data_provider()
89 | {
90 | return array(array('"null"', null, ''),
91 |
92 | array('"boolean"', true, "\001"),
93 | array('"boolean"', false, "\000"),
94 |
95 | array('"int"', (int) -2147483648, "\xFF\xFF\xFF\xFF\x0F"),
96 | array('"int"', -1, "\001"),
97 | array('"int"', 0, "\000"),
98 | array('"int"', 1, "\002"),
99 | array('"int"', 2147483647, "\xFE\xFF\xFF\xFF\x0F"),
100 |
101 | array('"long"', (int) -9223372036854775808, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"),
102 | array('"long"', -(1<<62), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"),
103 | array('"long"', -4294967295, "\xFD\xFF\xFF\xFF\x1F"),
104 | array('"long"', -10, "\x13"),
105 | array('"long"', -3, "\005"),
106 | array('"long"', -2, "\003"),
107 | array('"long"', -1, "\001"),
108 | array('"long"', 0, "\000"),
109 | array('"long"', 1, "\002"),
110 | array('"long"', 2, "\004"),
111 | array('"long"', 3, "\006"),
112 | array('"long"', 10, "\x14"),
113 | array('"long"', 4294967295, "\xFE\xFF\xFF\xFF\x1F"),
114 | array('"long"', 1<<62, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01"),
115 | array('"long"', 9223372036854775807, "\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"),
116 |
117 | array('"float"', (float) -10.0, "\000\000 \301"),
118 | array('"float"', (float) -1.0, "\000\000\200\277"),
119 | array('"float"', (float) 0.0, "\000\000\000\000"),
120 | array('"float"', (float) 2.0, "\000\000\000@"),
121 | array('"float"', (float) 9.0, "\000\000\020A"),
122 |
123 | array('"double"', (double) -10.0, "\000\000\000\000\000\000$\300"),
124 | array('"double"', (double) -1.0, "\000\000\000\000\000\000\360\277"),
125 | array('"double"', (double) 0.0, "\000\000\000\000\000\000\000\000"),
126 | array('"double"', (double) 2.0, "\000\000\000\000\000\000\000@"),
127 | array('"double"', (double) 9.0, "\000\000\000\000\000\000\"@"),
128 |
129 | array('"string"', 'foo', "\x06foo"),
130 | array('"bytes"', "\x01\x02\x03", "\x06\x01\x02\x03"),
131 |
132 | array('{"type":"array","items":"int"}',
133 | array(1,2,3),
134 | "\x06\x02\x04\x06\x00"),
135 | array('{"type":"map","values":"int"}',
136 | array('foo' => 1, 'bar' => 2, 'baz' => 3),
137 | "\x06\x06foo\x02\x06bar\x04\x06baz\x06\x00"),
138 | array('["null", "int"]', 1, "\x02\x02"),
139 | array('{"name":"fix","type":"fixed","size":3}',
140 | "\xAA\xBB\xCC", "\xAA\xBB\xCC"),
141 | array('{"name":"enm","type":"enum","symbols":["A","B","C"]}',
142 | 'B', "\x02"),
143 | array('{"name":"rec","type":"record","fields":[{"name":"a","type":"int"},{"name":"b","type":"boolean"}]}',
144 | array('a' => 1, 'b' => false),
145 | "\x02\x00"),
146 | array('{"type":"bytes","logicalType": "decimal","precision": 4,"scale": 1}',
147 | '1',
148 | "\x02\x0a"),
149 | array('{"type":"bytes","logicalType": "decimal","precision": 4,"scale": 1}',
150 | '-0.1',
151 | "\x02\xff"),
152 | array('{"type":"bytes","logicalType": "decimal","precision": 4,"scale": 1}',
153 | -0.1,
154 | "\x02\xff"),
155 | array('{"type":"bytes","logicalType": "decimal","precision": 4,"scale": 1}',
156 | 3.1,
157 | "\x02\x1f"),
158 | array('{"type":"bytes","logicalType": "decimal","precision": 4,"scale": 2}',
159 | 2.55,
160 | "\x04\x00\xff"),
161 | array('{"type":"bytes","logicalType": "decimal","precision": 4,"scale": 0}',
162 | -256,
163 | "\x04\xff\x00"),
164 | array('{"type":"bytes","logicalType": "decimal","precision": 4,"scale": 3}',
165 | 0.127,
166 | "\x02\x7f"),
167 | array('{"type":"bytes","logicalType": "decimal","precision": 19,"scale": 0}',
168 | PHP_INT_MAX,
169 | "\x10\x7f\xff\xff\xff\xff\xff\xff\xff"),
170 | array('{"type":"bytes","logicalType": "decimal","precision": 19,"scale": 0}',
171 | PHP_INT_MIN,
172 | "\x10\x80\x00\x00\x00\x00\x00\x00\x00")
173 | );
174 | }
175 |
176 | /**
177 | * @return array
178 | */
179 | function default_provider()
180 | {
181 | return array(array('"null"', 'null', null),
182 | array('"boolean"', 'true', true),
183 | array('"int"', '1', 1),
184 | array('"long"', '2000', 2000),
185 | array('"float"', '1.1', (float) 1.1),
186 | array('"double"', '200.2', (double) 200.2),
187 | array('"string"', '"quux"', 'quux'),
188 | array('"bytes"', '"\u00FF"', "\xC3\xBF"),
189 | array('{"type":"array","items":"int"}',
190 | '[5,4,3,2]', array(5,4,3,2)),
191 | array('{"type":"map","values":"int"}',
192 | '{"a":9}', array('a' => 9)),
193 | array('["int","string"]', '8', 8),
194 | array('{"name":"x","type":"enum","symbols":["A","V"]}',
195 | '"A"', 'A'),
196 | array('{"name":"x","type":"fixed","size":4}', '"\u00ff"', "\xC3\xBF"),
197 | array('{"name":"x","type":"record","fields":[{"name":"label","type":"int"}]}',
198 | '{"label":7}', array('label' => 7)));
199 | }
200 |
201 | /**
202 | * @dataProvider default_provider
203 | * @param $field_schema_json
204 | * @param $default_json
205 | * @param $default_value
206 | */
207 | function test_field_default_value($field_schema_json,
208 | $default_json, $default_value)
209 | {
210 | $writers_schema_json = '{"name":"foo","type":"record","fields":[]}';
211 | $writers_schema = AvroSchema::parse($writers_schema_json);
212 |
213 | $readers_schema_json = sprintf(
214 | '{"name":"foo","type":"record","fields":[{"name":"f","type":%s,"default":%s}]}',
215 | $field_schema_json, $default_json);
216 | $readers_schema = AvroSchema::parse($readers_schema_json);
217 |
218 | $reader = new AvroIODatumReader($writers_schema, $readers_schema);
219 | $record = $reader->read(new AvroIOBinaryDecoder(new AvroStringIO()));
220 | if (array_key_exists('f', $record))
221 | $this->assertEquals($default_value, $record['f']);
222 | else
223 | $this->assertTrue(false, sprintf('expected field record[f]: %s',
224 | print_r($record, true))) ;
225 | }
226 |
227 | }
228 |
--------------------------------------------------------------------------------
/test/FileIOTest.php:
--------------------------------------------------------------------------------
1 | getTmpFile();
30 | if (file_exists($file))
31 | unlink($file);
32 | }
33 |
34 | private function getFileName()
35 | {
36 | return __DIR__ . '/data/users.avro';
37 | }
38 |
39 | private function getTmpFile()
40 | {
41 | return __DIR__ . '/tmp/users.avro';
42 | }
43 |
44 | private function read()
45 | {
46 | $fileName = $this->getFileName();
47 | $reader = AvroDataIO::open_file($fileName);
48 | return $reader->data();
49 | }
50 |
51 | private function readIterator()
52 | {
53 | $fileName = $this->getFileName();
54 | $reader = AvroDataIO::open_file($fileName);
55 | $data = [];
56 | foreach ($reader->data_iterator() as $row) {
57 | $data[] = $row;
58 | }
59 | return $data;
60 | }
61 |
62 | public function testReading()
63 | {
64 | $expected = [
65 | [
66 | 'name' => 'Alyssa',
67 | 'favorite_color' => null,
68 | 'favorite_numbers' => [3, 9, 15, 20],
69 | ],
70 | [
71 | 'name' => 'Ben',
72 | 'favorite_color' => 'red',
73 | 'favorite_numbers' => [],
74 | ]
75 | ];
76 |
77 | // Classic loading.
78 | $this->assertEquals($expected, $this->read());
79 |
80 | // Iterator loading.
81 | $this->assertEquals($expected, $this->readIterator());
82 | }
83 |
84 | /**
85 | * Doesn't work because due to Avro format peculiarities mean that no two
86 | * encodings of the same data will be binary equal.
87 | */
88 | public function disabled_testRoundTrip()
89 | {
90 | $inFile = $this->getFileName();
91 | $outFile = $this->getTmpFile();
92 | $schemaFile = __DIR__ . '/data/user.avsc';
93 | $data = $this->read();
94 | $schema = file_get_contents($schemaFile);
95 | $writer = AvroDataIO::open_file($outFile, 'w', $schema);
96 | foreach ($data as $record)
97 | {
98 | $writer->append($record);
99 | }
100 | $writer->close();
101 |
102 | $oldData = file_get_contents($inFile);
103 | $newData = file_get_contents($outFile);
104 | if ($oldData !== $newData)
105 | {
106 | $diff = shell_exec("bash -c \"diff -y -W 150 <(xxd '$inFile') <(xxd '$outFile')\"");
107 | $this->fail("Round trip failed, files not equal:\n$diff");
108 | }
109 | $this->assertTrue(true, 'Dummy assert to prevent this test from being marked as risky');
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/test/FloatIntEncodingTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(is_float(self::$FLOAT_NAN), 'float NaN is a float');
68 | $this->assertTrue(is_nan(self::$FLOAT_NAN), 'float NaN is NaN');
69 | $this->assertFalse(is_infinite(self::$FLOAT_NAN), 'float NaN is not infinite');
70 |
71 | $this->assertTrue(is_float(self::$FLOAT_POS_INF), 'float pos infinity is a float');
72 | $this->assertTrue(is_infinite(self::$FLOAT_POS_INF), 'float pos infinity is infinite');
73 | $this->assertTrue(0 < self::$FLOAT_POS_INF, 'float pos infinity is greater than 0');
74 | $this->assertFalse(is_nan(self::$FLOAT_POS_INF), 'float pos infinity is not NaN');
75 |
76 | $this->assertTrue(is_float(self::$FLOAT_NEG_INF), 'float neg infinity is a float');
77 | $this->assertTrue(is_infinite(self::$FLOAT_NEG_INF), 'float neg infinity is infinite');
78 | $this->assertTrue(0 > self::$FLOAT_NEG_INF, 'float neg infinity is less than 0');
79 | $this->assertFalse(is_nan(self::$FLOAT_NEG_INF), 'float neg infinity is not NaN');
80 |
81 | $this->assertTrue(is_double(self::$DOUBLE_NAN), 'double NaN is a double');
82 | $this->assertTrue(is_nan(self::$DOUBLE_NAN), 'double NaN is NaN');
83 | $this->assertFalse(is_infinite(self::$DOUBLE_NAN), 'double NaN is not infinite');
84 |
85 | $this->assertTrue(is_double(self::$DOUBLE_POS_INF), 'double pos infinity is a double');
86 | $this->assertTrue(is_infinite(self::$DOUBLE_POS_INF), 'double pos infinity is infinite');
87 | $this->assertTrue(0 < self::$DOUBLE_POS_INF, 'double pos infinity is greater than 0');
88 | $this->assertFalse(is_nan(self::$DOUBLE_POS_INF), 'double pos infinity is not NaN');
89 |
90 | $this->assertTrue(is_double(self::$DOUBLE_NEG_INF), 'double neg infinity is a double');
91 | $this->assertTrue(is_infinite(self::$DOUBLE_NEG_INF), 'double neg infinity is infinite');
92 | $this->assertTrue(0 > self::$DOUBLE_NEG_INF, 'double neg infinity is less than 0');
93 | $this->assertFalse(is_nan(self::$DOUBLE_NEG_INF), 'double neg infinity is not NaN');
94 |
95 | }
96 |
97 | /**
98 | * @return array
99 | */
100 | function special_vals_provider()
101 | {
102 | self::make_special_vals();
103 | return array(array(self::DOUBLE_TYPE, self::$DOUBLE_POS_INF, self::$LONG_BITS_POS_INF),
104 | array(self::DOUBLE_TYPE, self::$DOUBLE_NEG_INF, self::$LONG_BITS_NEG_INF),
105 | array(self::FLOAT_TYPE, self::$FLOAT_POS_INF, self::$INT_BITS_POS_INF),
106 | array(self::FLOAT_TYPE, self::$FLOAT_NEG_INF, self::$INT_BITS_NEG_INF));
107 | }
108 |
109 | /**
110 | * @dataProvider special_vals_provider
111 | * @param $type
112 | * @param $val
113 | * @param $bits
114 | */
115 | function test_encoding_special_values($type, $val, $bits)
116 | {
117 | $this->assert_encode_values($type, $val, $bits);
118 | }
119 |
120 | /**
121 | * @return array
122 | */
123 | function nan_vals_provider()
124 | {
125 | self::make_special_vals();
126 | return array(array(self::DOUBLE_TYPE, self::$DOUBLE_NAN, self::$LONG_BITS_NAN),
127 | array(self::FLOAT_TYPE, self::$FLOAT_NAN, self::$INT_BITS_NAN));
128 | }
129 |
130 | /**
131 | * @dataProvider nan_vals_provider
132 | * @param $type
133 | * @param $val
134 | * @param $bits
135 | */
136 | function test_encoding_nan_values($type, $val, $bits)
137 | {
138 | $this->assert_encode_nan_values($type, $val, $bits);
139 | }
140 |
141 | /**
142 | * @return array
143 | */
144 | function normal_vals_provider()
145 | {
146 | return array(
147 | array(self::DOUBLE_TYPE, (double) -10, "\000\000\000\000\000\000$\300", '000000000000420c'),
148 | array(self::DOUBLE_TYPE, (double) -9, "\000\000\000\000\000\000\"\300", '000000000000220c'),
149 | array(self::DOUBLE_TYPE, (double) -8, "\000\000\000\000\000\000 \300", '000000000000020c'),
150 | array(self::DOUBLE_TYPE, (double) -7, "\000\000\000\000\000\000\034\300", '000000000000c10c'),
151 | array(self::DOUBLE_TYPE, (double) -6, "\000\000\000\000\000\000\030\300", '000000000000810c'),
152 | array(self::DOUBLE_TYPE, (double) -5, "\000\000\000\000\000\000\024\300", '000000000000410c'),
153 | array(self::DOUBLE_TYPE, (double) -4, "\000\000\000\000\000\000\020\300", '000000000000010c'),
154 | /**/ array(self::DOUBLE_TYPE, (double) -3, "\000\000\000\000\000\000\010\300", '000000000000800c'),
155 | array(self::DOUBLE_TYPE, (double) -2, "\000\000\000\000\000\000\000\300", '000000000000000c'),
156 | array(self::DOUBLE_TYPE, (double) -1, "\000\000\000\000\000\000\360\277", '0000000000000ffb'),
157 | array(self::DOUBLE_TYPE, (double) 0, "\000\000\000\000\000\000\000\000", '0000000000000000'),
158 | array(self::DOUBLE_TYPE, (double) 1, "\000\000\000\000\000\000\360?", '0000000000000ff3'),
159 | array(self::DOUBLE_TYPE, (double) 2, "\000\000\000\000\000\000\000@", '0000000000000004'),
160 | /**/ array(self::DOUBLE_TYPE, (double) 3, "\000\000\000\000\000\000\010@", '0000000000008004'),
161 | array(self::DOUBLE_TYPE, (double) 4, "\000\000\000\000\000\000\020@", '0000000000000104'),
162 | array(self::DOUBLE_TYPE, (double) 5, "\000\000\000\000\000\000\024@", '0000000000004104'),
163 | array(self::DOUBLE_TYPE, (double) 6, "\000\000\000\000\000\000\030@", '0000000000008104'),
164 | array(self::DOUBLE_TYPE, (double) 7, "\000\000\000\000\000\000\034@", '000000000000c104'),
165 | array(self::DOUBLE_TYPE, (double) 8, "\000\000\000\000\000\000 @", '0000000000000204'),
166 | array(self::DOUBLE_TYPE, (double) 9, "\000\000\000\000\000\000\"@", '0000000000002204'),
167 | array(self::DOUBLE_TYPE, (double) 10, "\000\000\000\000\000\000$@", '0000000000004204'),
168 | /**/ array(self::DOUBLE_TYPE, (double) -1234.2132, "\007\316\031Q\332H\223\300", '70ec9115ad84390c'),
169 | array(self::DOUBLE_TYPE, (double) -2.11e+25, "\311\260\276J\031t1\305", '9c0beba49147135c'),
170 |
171 | array(self::FLOAT_TYPE, (float) -10, "\000\000 \301", '0000021c'),
172 | array(self::FLOAT_TYPE, (float) -9, "\000\000\020\301", '0000011c'),
173 | array(self::FLOAT_TYPE, (float) -8, "\000\000\000\301", '0000001c'),
174 | array(self::FLOAT_TYPE, (float) -7, "\000\000\340\300", '00000e0c'),
175 | array(self::FLOAT_TYPE, (float) -6, "\000\000\300\300", '00000c0c'),
176 | array(self::FLOAT_TYPE, (float) -5, "\000\000\240\300", '00000a0c'),
177 | array(self::FLOAT_TYPE, (float) -4, "\000\000\200\300", '0000080c'),
178 | array(self::FLOAT_TYPE, (float) -3, "\000\000@\300", '0000040c'),
179 | array(self::FLOAT_TYPE, (float) -2, "\000\000\000\300", '0000000c'),
180 | array(self::FLOAT_TYPE, (float) -1, "\000\000\200\277", '000008fb'),
181 | array(self::FLOAT_TYPE, (float) 0, "\000\000\000\000", '00000000'),
182 | array(self::FLOAT_TYPE, (float) 1, "\000\000\200?", '000008f3'),
183 | array(self::FLOAT_TYPE, (float) 2, "\000\000\000@", '00000004'),
184 | array(self::FLOAT_TYPE, (float) 3, "\000\000@@", '00000404'),
185 | array(self::FLOAT_TYPE, (float) 4, "\000\000\200@", '00000804'),
186 | array(self::FLOAT_TYPE, (float) 5, "\000\000\240@", '00000a04'),
187 | array(self::FLOAT_TYPE, (float) 6, "\000\000\300@", '00000c04'),
188 | array(self::FLOAT_TYPE, (float) 7, "\000\000\340@", '00000e04'),
189 | array(self::FLOAT_TYPE, (float) 8, "\000\000\000A", '00000014'),
190 | array(self::FLOAT_TYPE, (float) 9, "\000\000\020A", '00000114'),
191 | array(self::FLOAT_TYPE, (float) 10, "\000\000 A", '00000214'),
192 | array(self::FLOAT_TYPE, (float) -1234.5, "\000P\232\304", '0005a94c'),
193 | array(self::FLOAT_TYPE, (float) -211300000.0, "\352\202I\315", 'ae2894dc'),
194 | );
195 | }
196 |
197 | /**
198 | * @return array
199 | */
200 | function float_vals_provider()
201 | {
202 | $ary = array();
203 |
204 | foreach ($this->normal_vals_provider() as $values)
205 | if (self::FLOAT_TYPE == $values[0])
206 | $ary []= array($values[0], $values[1], $values[2]);
207 |
208 | return $ary;
209 | }
210 |
211 | /**
212 | * @return array
213 | */
214 | function double_vals_provider()
215 | {
216 | $ary = array();
217 |
218 | foreach ($this->normal_vals_provider() as $values)
219 | if (self::DOUBLE_TYPE == $values[0])
220 | $ary []= array($values[0], $values[1], $values[2]);
221 |
222 | return $ary;
223 | }
224 |
225 |
226 | /**
227 | * @dataProvider float_vals_provider
228 | * @param $type
229 | * @param $val
230 | * @param $bits
231 | */
232 | function test_encoding_float_values($type, $val, $bits)
233 | {
234 | $this->assert_encode_values($type, $val, $bits);
235 | }
236 |
237 | /**
238 | * @dataProvider double_vals_provider
239 | * @param $type
240 | * @param $val
241 | * @param $bits
242 | */
243 | function test_encoding_double_values($type, $val, $bits)
244 | {
245 | $this->assert_encode_values($type, $val, $bits);
246 | }
247 |
248 | /**
249 | * @param $type
250 | * @param $val
251 | * @param $bits
252 | */
253 | function assert_encode_values($type, $val, $bits)
254 | {
255 | if (self::FLOAT_TYPE == $type)
256 | {
257 | $decoder = array('AvroIOBinaryDecoder', 'int_bits_to_float');
258 | $encoder = array('AvroIOBinaryEncoder', 'float_to_int_bits');
259 | }
260 | else
261 | {
262 | $decoder = array('AvroIOBinaryDecoder', 'long_bits_to_double');
263 | $encoder = array('AvroIOBinaryEncoder', 'double_to_long_bits');
264 | }
265 |
266 | $decoded_bits_val = call_user_func($decoder, $bits);
267 | $this->assertEquals($val, $decoded_bits_val,
268 | sprintf("%s\n expected: '%f'\n given: '%f'",
269 | 'DECODED BITS', $val, $decoded_bits_val));
270 |
271 | $encoded_val_bits = call_user_func($encoder, $val);
272 | $this->assertEquals($bits, $encoded_val_bits,
273 | sprintf("%s\n expected: '%s'\n given: '%s'",
274 | 'ENCODED VAL',
275 | AvroDebug::hex_string($bits),
276 | AvroDebug::hex_string($encoded_val_bits)));
277 |
278 | $round_trip_value = call_user_func($decoder, $encoded_val_bits);
279 | $this->assertEquals($val, $round_trip_value,
280 | sprintf("%s\n expected: '%f'\n given: '%f'",
281 | 'ROUND TRIP BITS', $val, $round_trip_value));
282 | }
283 |
284 | /**
285 | * @param $type
286 | * @param $val
287 | * @param $bits
288 | */
289 | function assert_encode_nan_values($type, $val, $bits)
290 | {
291 | if (self::FLOAT_TYPE == $type)
292 | {
293 | $decoder = array('AvroIOBinaryDecoder', 'int_bits_to_float');
294 | $encoder = array('AvroIOBinaryEncoder', 'float_to_int_bits');
295 | }
296 | else
297 | {
298 | $decoder = array('AvroIOBinaryDecoder', 'long_bits_to_double');
299 | $encoder = array('AvroIOBinaryEncoder', 'double_to_long_bits');
300 | }
301 |
302 | $decoded_bits_val = call_user_func($decoder, $bits);
303 | $this->assertTrue(is_nan($decoded_bits_val),
304 | sprintf("%s\n expected: '%f'\n given: '%f'",
305 | 'DECODED BITS', $val, $decoded_bits_val));
306 |
307 | $encoded_val_bits = call_user_func($encoder, $val);
308 | $this->assertEquals($bits, $encoded_val_bits,
309 | sprintf("%s\n expected: '%s'\n given: '%s'",
310 | 'ENCODED VAL',
311 | AvroDebug::hex_string($bits),
312 | AvroDebug::hex_string($encoded_val_bits)));
313 |
314 | $round_trip_value = call_user_func($decoder, $encoded_val_bits);
315 | $this->assertTrue(is_nan($round_trip_value),
316 | sprintf("%s\n expected: '%f'\n given: '%f'",
317 | 'ROUND TRIP BITS', $val, $round_trip_value));
318 | }
319 |
320 | }
321 |
--------------------------------------------------------------------------------
/test/IODatumReaderTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(AvroIODatumReader::schemas_match(
36 | AvroSchema::parse($writers_schema),
37 | AvroSchema::parse($readers_schema)));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/LongEncodingTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Requires 64-bit platform');
40 | }
41 |
42 | function skip_if_no_gmp()
43 | {
44 | if (!extension_loaded('gmp'))
45 | $this->markTestSkipped('Requires GMP PHP Extension.');
46 | }
47 |
48 | /**
49 | * @param $expected
50 | * @param $actual
51 | * @param $shift_type
52 | * @param $expected_binary
53 | * @param $actual_binary
54 | */
55 | function assert_bit_shift($expected, $actual, $shift_type,
56 | $expected_binary, $actual_binary)
57 | {
58 | $this->assertEquals(
59 | $expected, $actual,
60 | sprintf("%s\nexpected: %d\n actual: %d\nexpected b: %s\n actual b: %s",
61 | $shift_type, $expected, $actual,
62 | $expected_binary, $actual_binary));
63 | }
64 |
65 | /**
66 | * @dataProvider bit_shift_provider
67 | * @param $val
68 | * @param $shift
69 | * @param $expected_lval
70 | * @param $expected_rval
71 | * @param $lbin
72 | * @param $rbin
73 | */
74 | function test_bit_shift($val, $shift, $expected_lval, $expected_rval, $lbin, $rbin)
75 | {
76 |
77 | $this->skip_64_bit_test_on_32_bit();
78 |
79 | $lval = (int) ((int) $val << $shift);
80 | $this->assert_bit_shift($expected_lval, strval($lval),
81 | 'lshift', $lbin, decbin($lval));
82 | $rval = ((int) $val >> $shift);
83 | $this->assert_bit_shift($expected_rval, strval($rval),
84 | 'rshift', $rbin, decbin($rval));
85 | }
86 |
87 | /**
88 | * @dataProvider bit_shift_provider
89 | * @param $val
90 | * @param $shift
91 | * @param $expected_lval
92 | * @param $expected_rval
93 | * @param $lbin
94 | * @param $rbin
95 | */
96 | function test_left_shift_gmp($val, $shift,
97 | $expected_lval, $expected_rval,
98 | $lbin, $rbin)
99 | {
100 | $this->skip_if_no_gmp();
101 | $lval = gmp_strval(AvroGMP::shift_left($val, $shift));
102 | $this->assert_bit_shift($expected_lval, $lval, 'gmp left shift',
103 | $lbin, decbin((int) $lval));
104 | }
105 |
106 | /**
107 | * @dataProvider bit_shift_provider
108 | * @param $val
109 | * @param $shift
110 | * @param $expected_lval
111 | * @param $expected_rval
112 | * @param $lbin
113 | * @param $rbin
114 | */
115 | function test_right_shift_gmp($val, $shift, $expected_lval, $expected_rval,
116 | $lbin, $rbin)
117 | {
118 | $this->skip_if_no_gmp();
119 | $rval = gmp_strval(AvroGMP::shift_right($val, $shift));
120 | $this->assert_bit_shift($expected_rval, $rval, 'gmp right shift',
121 | $rbin, decbin((int) $rval));
122 | }
123 |
124 | /**
125 | * @dataProvider long_provider
126 | * @param $val
127 | * @param $expected_bytes
128 | */
129 | function test_encode_long($val, $expected_bytes)
130 | {
131 | $this->skip_64_bit_test_on_32_bit();
132 | $bytes = AvroIOBinaryEncoder::encode_long($val);
133 | $this->assertEquals($expected_bytes, $bytes);
134 | }
135 |
136 | /**
137 | * @dataProvider long_provider
138 | * @param $val
139 | * @param $expected_bytes
140 | */
141 | function test_gmp_encode_long($val, $expected_bytes)
142 | {
143 | $this->skip_if_no_gmp();
144 | $bytes = AvroGMP::encode_long($val);
145 | $this->assertEquals($expected_bytes, $bytes);
146 | }
147 |
148 | /**
149 | * @dataProvider long_provider
150 | * @param $expected_val
151 | * @param $bytes
152 | */
153 | function test_decode_long_from_array($expected_val, $bytes)
154 | {
155 | $this->skip_64_bit_test_on_32_bit();
156 | $ary = array_map('ord', str_split($bytes));
157 | $val = AvroIOBinaryDecoder::decode_long_from_array($ary);
158 | $this->assertEquals($expected_val, $val);
159 | }
160 |
161 | /**
162 | * @dataProvider long_provider
163 | * @param $expected_val
164 | * @param $bytes
165 | */
166 | function test_gmp_decode_long_from_array($expected_val, $bytes)
167 | {
168 | $this->skip_if_no_gmp();
169 | $ary = array_map('ord', str_split($bytes));
170 | $val = AvroGMP::decode_long_from_array($ary);
171 | $this->assertEquals($expected_val, $val);
172 | }
173 |
174 | /**
175 | * @return array
176 | */
177 | function long_provider()
178 | {
179 | return array(array('0', "\x0"),
180 | array('1', "\x2"),
181 | array('7', "\xe"),
182 | array('10000', "\xa0\x9c\x1"),
183 | array('2147483647', "\xfe\xff\xff\xff\xf"),
184 | array('98765432109', "\xda\x94\x87\xee\xdf\x5"),
185 | array('-1', "\x1"),
186 | array('-7', "\xd"),
187 | array('-10000', "\x9f\x9c\x1"),
188 | array('-2147483648', "\xff\xff\xff\xff\xf"),
189 | array('-98765432109', "\xd9\x94\x87\xee\xdf\x5")
190 | );
191 |
192 | }
193 |
194 | /**
195 | * @return array
196 | */
197 | function bit_shift_provider()
198 | {
199 | // val shift lval rval
200 | return array(
201 | array('0', 0, '0', '0',
202 | '0',
203 | '0'),
204 | array('0', 1, '0', '0',
205 | '0',
206 | '0'),
207 | array('0', 7, '0', '0',
208 | '0',
209 | '0'),
210 | array('0', 63, '0', '0',
211 | '0',
212 | '0'),
213 | array('1', 0, '1', '1',
214 | '1',
215 | '1'),
216 | array('1', 1, '2', '0',
217 | '10',
218 | '0'),
219 | array('1', 7, '128', '0',
220 | '10000000',
221 | '0'),
222 | array('1', 63, '-9223372036854775808', '0',
223 | '1000000000000000000000000000000000000000000000000000000000000000',
224 | '0'),
225 | array('100', 0, '100', '100',
226 | '1100100',
227 | '1100100'),
228 | array('100', 1, '200', '50',
229 | '11001000',
230 | '110010'),
231 | array('100', 7, '12800', '0',
232 | '11001000000000',
233 | '0'),
234 | array('100', 63, '0', '0',
235 | '0',
236 | '0'),
237 | array('1000000', 0, '1000000', '1000000',
238 | '11110100001001000000',
239 | '11110100001001000000'),
240 | array('1000000', 1, '2000000', '500000',
241 | '111101000010010000000',
242 | '1111010000100100000'),
243 | array('1000000', 7, '128000000', '7812',
244 | '111101000010010000000000000',
245 | '1111010000100'),
246 | array('1000000', 63, '0', '0',
247 | '0',
248 | '0'),
249 | array('2147483647', 0, '2147483647', '2147483647',
250 | '1111111111111111111111111111111',
251 | '1111111111111111111111111111111'),
252 | array('2147483647', 1, '4294967294', '1073741823',
253 | '11111111111111111111111111111110',
254 | '111111111111111111111111111111'),
255 | array('2147483647', 7, '274877906816', '16777215',
256 | '11111111111111111111111111111110000000',
257 | '111111111111111111111111'),
258 | array('2147483647', 63, '-9223372036854775808', '0',
259 | '1000000000000000000000000000000000000000000000000000000000000000',
260 | '0'),
261 | array('10000000000', 0, '10000000000', '10000000000',
262 | '1001010100000010111110010000000000',
263 | '1001010100000010111110010000000000'),
264 | array('10000000000', 1, '20000000000', '5000000000',
265 | '10010101000000101111100100000000000',
266 | '100101010000001011111001000000000'),
267 | array('10000000000', 7, '1280000000000', '78125000',
268 | '10010101000000101111100100000000000000000',
269 | '100101010000001011111001000'),
270 | array('10000000000', 63, '0', '0',
271 | '0',
272 | '0'),
273 | array('9223372036854775807', 0, '9223372036854775807', '9223372036854775807',
274 | '111111111111111111111111111111111111111111111111111111111111111',
275 | '111111111111111111111111111111111111111111111111111111111111111'),
276 | array('9223372036854775807', 1, '-2', '4611686018427387903',
277 | '1111111111111111111111111111111111111111111111111111111111111110',
278 | '11111111111111111111111111111111111111111111111111111111111111'),
279 | array('9223372036854775807', 7, '-128', '72057594037927935',
280 | '1111111111111111111111111111111111111111111111111111111110000000',
281 | '11111111111111111111111111111111111111111111111111111111'),
282 | array('9223372036854775807', 63, '-9223372036854775808', '0',
283 | '1000000000000000000000000000000000000000000000000000000000000000',
284 | '0'),
285 | array('-1', 0, '-1', '-1',
286 | '1111111111111111111111111111111111111111111111111111111111111111',
287 | '1111111111111111111111111111111111111111111111111111111111111111'),
288 | array('-1', 1, '-2', '-1',
289 | '1111111111111111111111111111111111111111111111111111111111111110',
290 | '1111111111111111111111111111111111111111111111111111111111111111'),
291 | array('-1', 7, '-128', '-1',
292 | '1111111111111111111111111111111111111111111111111111111110000000',
293 | '1111111111111111111111111111111111111111111111111111111111111111'),
294 | array('-1', 63, '-9223372036854775808', '-1',
295 | '1000000000000000000000000000000000000000000000000000000000000000',
296 | '1111111111111111111111111111111111111111111111111111111111111111'),
297 | array('-100', 0, '-100', '-100',
298 | '1111111111111111111111111111111111111111111111111111111110011100',
299 | '1111111111111111111111111111111111111111111111111111111110011100'),
300 | array('-100', 1, '-200', '-50',
301 | '1111111111111111111111111111111111111111111111111111111100111000',
302 | '1111111111111111111111111111111111111111111111111111111111001110'),
303 | array('-100', 7, '-12800', '-1',
304 | '1111111111111111111111111111111111111111111111111100111000000000',
305 | '1111111111111111111111111111111111111111111111111111111111111111'),
306 | array('-100', 63, '0', '-1',
307 | '0',
308 | '1111111111111111111111111111111111111111111111111111111111111111'),
309 | array('-1000000', 0, '-1000000', '-1000000',
310 | '1111111111111111111111111111111111111111111100001011110111000000',
311 | '1111111111111111111111111111111111111111111100001011110111000000'),
312 | array('-1000000', 1, '-2000000', '-500000',
313 | '1111111111111111111111111111111111111111111000010111101110000000',
314 | '1111111111111111111111111111111111111111111110000101111011100000'),
315 | array('-1000000', 7, '-128000000', '-7813',
316 | '1111111111111111111111111111111111111000010111101110000000000000',
317 | '1111111111111111111111111111111111111111111111111110000101111011'),
318 | array('-1000000', 63, '0', '-1',
319 | '0',
320 | '1111111111111111111111111111111111111111111111111111111111111111'),
321 | array('-2147483648', 0, '-2147483648', '-2147483648',
322 | '1111111111111111111111111111111110000000000000000000000000000000',
323 | '1111111111111111111111111111111110000000000000000000000000000000'),
324 | array('-2147483648', 1, '-4294967296', '-1073741824',
325 | '1111111111111111111111111111111100000000000000000000000000000000',
326 | '1111111111111111111111111111111111000000000000000000000000000000'),
327 | array('-2147483648', 7, '-274877906944', '-16777216',
328 | '1111111111111111111111111100000000000000000000000000000000000000',
329 | '1111111111111111111111111111111111111111000000000000000000000000'),
330 | array('-2147483648', 63, '0', '-1',
331 | '0',
332 | '1111111111111111111111111111111111111111111111111111111111111111'),
333 | array('-10000000000', 0, '-10000000000', '-10000000000',
334 | '1111111111111111111111111111110110101011111101000001110000000000',
335 | '1111111111111111111111111111110110101011111101000001110000000000'),
336 | array('-10000000000', 1, '-20000000000', '-5000000000',
337 | '1111111111111111111111111111101101010111111010000011100000000000',
338 | '1111111111111111111111111111111011010101111110100000111000000000'),
339 | array('-10000000000', 7, '-1280000000000', '-78125000',
340 | '1111111111111111111111101101010111111010000011100000000000000000',
341 | '1111111111111111111111111111111111111011010101111110100000111000'),
342 | array('-10000000000', 63, '0', '-1',
343 | '0',
344 | '1111111111111111111111111111111111111111111111111111111111111111'),
345 | array('-9223372036854775808', 0, '-9223372036854775808', '-9223372036854775808',
346 | '1000000000000000000000000000000000000000000000000000000000000000',
347 | '1000000000000000000000000000000000000000000000000000000000000000'),
348 | array('-9223372036854775808', 1, '0', '-4611686018427387904',
349 | '0',
350 | '1100000000000000000000000000000000000000000000000000000000000000'),
351 | array('-9223372036854775808', 7, '0', '-72057594037927936',
352 | '0',
353 | '1111111100000000000000000000000000000000000000000000000000000000'),
354 | array('-9223372036854775808', 63, '0', '-1',
355 | '0',
356 | '1111111111111111111111111111111111111111111111111111111111111111'),
357 | );
358 | }
359 |
360 | }
361 |
--------------------------------------------------------------------------------
/test/NameTest.php:
--------------------------------------------------------------------------------
1 | name = $name;
45 | $this->namespace = $namespace;
46 | $this->default_namespace = $default_namespace;
47 | $this->is_valid = $is_valid;
48 | $this->expected_fullname = $expected_fullname;
49 | }
50 |
51 | /**
52 | * @return mixed
53 | */
54 | function __toString()
55 | {
56 | return var_export($this, true);
57 | }
58 | }
59 |
60 | /**
61 | * Class NameTest
62 | */
63 | class NameTest extends \PHPUnit\Framework\TestCase
64 | {
65 |
66 | /**
67 | * @return array
68 | */
69 | function fullname_provider()
70 | {
71 | $examples = array(new NameExample('foo', null, null, true, 'foo'),
72 | new NameExample('foo', 'bar', null, true, 'bar.foo'),
73 | new NameExample('bar.foo', 'baz', null, true, 'bar.foo'),
74 | new NameExample('_bar.foo', 'baz', null, true, '_bar.foo'),
75 | new NameExample('bar._foo', 'baz', null, true, 'bar._foo'),
76 | new NameExample('3bar.foo', 'baz', null, false),
77 | new NameExample('bar.3foo', 'baz', null, false),
78 | new NameExample('b4r.foo', 'baz', null, true, 'b4r.foo'),
79 | new NameExample('bar.f0o', 'baz', null, true, 'bar.f0o'),
80 | new NameExample(' .foo', 'baz', null, false),
81 | new NameExample('bar. foo', 'baz', null, false),
82 | new NameExample('bar. ', 'baz', null, false)
83 | );
84 | $exes = array();
85 | foreach ($examples as $ex)
86 | $exes []= array($ex);
87 | return $exes;
88 | }
89 |
90 | /**
91 | * @dataProvider fullname_provider
92 | * @param $ex
93 | */
94 | function test_fullname($ex)
95 | {
96 | try
97 | {
98 | $name = new AvroName($ex->name, $ex->namespace, $ex->default_namespace);
99 | $this->assertTrue($ex->is_valid);
100 | $this->assertEquals($ex->expected_fullname, $name->fullname());
101 | }
102 | catch (AvroSchemaParseException $e)
103 | {
104 | $this->assertFalse($ex->is_valid, sprintf("%s:\n%s",
105 | $ex,
106 | $e->getMessage()));
107 | }
108 | }
109 |
110 | /**
111 | * @return array
112 | */
113 | function name_provider()
114 | {
115 | return array(array('a', true),
116 | array('_', true),
117 | array('1a', false),
118 | array('', false),
119 | array(null, false),
120 | array(' ', false),
121 | array('Cons', true));
122 | }
123 |
124 | /**
125 | * @dataProvider name_provider
126 | * @param $name
127 | * @param $is_well_formed
128 | */
129 | function test_name($name, $is_well_formed)
130 | {
131 | $this->assertEquals(AvroName::is_well_formed_name($name), $is_well_formed, $name ?? "null");
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/test/ProtocolFileTest.php:
--------------------------------------------------------------------------------
1 | prot_parseable);
33 | for ($i=0; $i<$cnt; $i++) {
34 | try {
35 | AvroProtocol::parse($this->prot_data[$i]);
36 | } catch (AvroSchemaParseException $x) {
37 | // exception ok if we expected this protocol spec to be unparseable
38 | $this->assertEquals(false, $this->prot_parseable[$i]);
39 | }
40 | }
41 | }
42 |
43 | // test data
44 | private $prot_parseable=array(true, true, true, true, true, true, false, true, true);
45 | private $prot_data = array(
46 | <<<'DATUM'
47 | {
48 | "namespace": "com.acme",
49 | "protocol": "HelloWorld",
50 |
51 | "types": [
52 | {"name": "Greeting", "type": "record", "fields": [
53 | {"name": "message", "type": "string"}]},
54 | {"name": "Curse", "type": "error", "fields": [
55 | {"name": "message", "type": "string"}]}
56 | ],
57 |
58 | "messages": {
59 | "hello": {
60 | "request": [{"name": "greeting", "type": "Greeting" }],
61 | "response": "Greeting",
62 | "errors": ["Curse"]
63 | }
64 | }
65 | }
66 | DATUM
67 | ,
68 | <<<'DATUM'
69 | {"namespace": "org.apache.avro.test",
70 | "protocol": "Simple",
71 |
72 | "types": [
73 | {"name": "Kind", "type": "enum", "symbols": ["FOO","BAR","BAZ"]},
74 |
75 | {"name": "MD5", "type": "fixed", "size": 16},
76 |
77 | {"name": "TestRecord", "type": "record",
78 | "fields": [
79 | {"name": "name", "type": "string", "order": "ignore"},
80 | {"name": "kind", "type": "Kind", "order": "descending"},
81 | {"name": "hash", "type": "MD5"}
82 | ]
83 | },
84 |
85 | {"name": "TestError", "type": "error", "fields": [
86 | {"name": "message", "type": "string"}
87 | ]
88 | }
89 |
90 | ],
91 |
92 | "messages": {
93 |
94 | "hello": {
95 | "request": [{"name": "greeting", "type": "string"}],
96 | "response": "string"
97 | },
98 |
99 | "echo": {
100 | "request": [{"name": "record", "type": "TestRecord"}],
101 | "response": "TestRecord"
102 | },
103 |
104 | "add": {
105 | "request": [{"name": "arg1", "type": "int"}, {"name": "arg2", "type": "int"}],
106 | "response": "int"
107 | },
108 |
109 | "echoBytes": {
110 | "request": [{"name": "data", "type": "bytes"}],
111 | "response": "bytes"
112 | },
113 |
114 | "error": {
115 | "request": [],
116 | "response": "null",
117 | "errors": ["TestError"]
118 | }
119 | }
120 |
121 | }
122 | DATUM
123 | ,
124 | <<<'DATUM'
125 | {"namespace": "org.apache.avro.test.namespace",
126 | "protocol": "TestNamespace",
127 |
128 | "types": [
129 | {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16},
130 | {"name": "TestRecord", "type": "record",
131 | "fields": [ {"name": "hash", "type": "org.apache.avro.test.util.MD5"} ]
132 | },
133 | {"name": "TestError", "namespace": "org.apache.avro.test.errors",
134 | "type": "error", "fields": [ {"name": "message", "type": "string"} ]
135 | }
136 | ],
137 |
138 | "messages": {
139 | "echo": {
140 | "request": [{"name": "record", "type": "TestRecord"}],
141 | "response": "TestRecord"
142 | },
143 |
144 | "error": {
145 | "request": [],
146 | "response": "null",
147 | "errors": ["org.apache.avro.test.errors.TestError"]
148 | }
149 |
150 | }
151 |
152 | }
153 | DATUM
154 | ,
155 | <<<'DATUM'
156 | {"namespace": "org.apache.avro.test.namespace",
157 | "protocol": "TestImplicitNamespace",
158 |
159 | "types": [
160 | {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16},
161 | {"name": "ReferencedRecord", "type": "record",
162 | "fields": [ {"name": "foo", "type": "string"} ] },
163 | {"name": "TestRecord", "type": "record",
164 | "fields": [ {"name": "hash", "type": "org.apache.avro.test.util.MD5"},
165 | {"name": "unqalified", "type": "ReferencedRecord"} ]
166 | },
167 | {"name": "TestError",
168 | "type": "error", "fields": [ {"name": "message", "type": "string"} ]
169 | }
170 | ],
171 |
172 | "messages": {
173 | "echo": {
174 | "request": [{"name": "qualified",
175 | "type": "org.apache.avro.test.namespace.TestRecord"}],
176 | "response": "TestRecord"
177 | },
178 |
179 | "error": {
180 | "request": [],
181 | "response": "null",
182 | "errors": ["org.apache.avro.test.namespace.TestError"]
183 | }
184 |
185 | }
186 |
187 | }
188 | DATUM
189 | ,
190 | <<<'DATUM'
191 | {"namespace": "org.apache.avro.test.namespace",
192 | "protocol": "TestNamespaceTwo",
193 |
194 | "types": [
195 | {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16},
196 | {"name": "ReferencedRecord", "type": "record",
197 | "namespace": "org.apache.avro.other.namespace",
198 | "fields": [ {"name": "foo", "type": "string"} ] },
199 | {"name": "TestRecord", "type": "record",
200 | "fields": [ {"name": "hash", "type": "org.apache.avro.test.util.MD5"},
201 | {"name": "qualified",
202 | "type": "org.apache.avro.other.namespace.ReferencedRecord"}
203 | ]
204 | },
205 | {"name": "TestError",
206 | "type": "error", "fields": [ {"name": "message", "type": "string"} ]
207 | }
208 | ],
209 |
210 | "messages": {
211 | "echo": {
212 | "request": [{"name": "qualified",
213 | "type": "org.apache.avro.test.namespace.TestRecord"}],
214 | "response": "TestRecord"
215 | },
216 |
217 | "error": {
218 | "request": [],
219 | "response": "null",
220 | "errors": ["org.apache.avro.test.namespace.TestError"]
221 | }
222 |
223 | }
224 |
225 | }
226 | DATUM
227 | ,
228 | <<<'DATUM'
229 | {"namespace": "org.apache.avro.test.namespace",
230 | "protocol": "TestValidRepeatedName",
231 |
232 | "types": [
233 | {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16},
234 | {"name": "ReferencedRecord", "type": "record",
235 | "namespace": "org.apache.avro.other.namespace",
236 | "fields": [ {"name": "foo", "type": "string"} ] },
237 | {"name": "ReferencedRecord", "type": "record",
238 | "fields": [ {"name": "bar", "type": "double"} ] },
239 | {"name": "TestError",
240 | "type": "error", "fields": [ {"name": "message", "type": "string"} ]
241 | }
242 | ],
243 |
244 | "messages": {
245 | "echo": {
246 | "request": [{"name": "qualified",
247 | "type": "ReferencedRecord"}],
248 | "response": "org.apache.avro.other.namespace.ReferencedRecord"
249 | },
250 |
251 | "error": {
252 | "request": [],
253 | "response": "null",
254 | "errors": ["org.apache.avro.test.namespace.TestError"]
255 | }
256 |
257 | }
258 |
259 | }
260 | DATUM
261 | ,
262 | <<<'DATUM'
263 | {"namespace": "org.apache.avro.test.namespace",
264 | "protocol": "TestInvalidRepeatedName",
265 |
266 | "types": [
267 | {"name": "org.apache.avro.test.util.MD5", "type": "fixed", "size": 16},
268 | {"name": "ReferencedRecord", "type": "record",
269 | "fields": [ {"name": "foo", "type": "string"} ] },
270 | {"name": "ReferencedRecord", "type": "record",
271 | "fields": [ {"name": "bar", "type": "double"} ] },
272 | {"name": "TestError",
273 | "type": "error", "fields": [ {"name": "message", "type": "string"} ]
274 | }
275 | ],
276 |
277 | "messages": {
278 | "echo": {
279 | "request": [{"name": "qualified",
280 | "type": "ReferencedRecord"}],
281 | "response": "org.apache.avro.other.namespace.ReferencedRecord"
282 | },
283 |
284 | "error": {
285 | "request": [],
286 | "response": "null",
287 | "errors": ["org.apache.avro.test.namespace.TestError"]
288 | }
289 |
290 | }
291 |
292 | }
293 | DATUM
294 | ,
295 | <<<'DATUM'
296 | {"namespace": "org.apache.avro.test",
297 | "protocol": "BulkData",
298 |
299 | "types": [],
300 |
301 | "messages": {
302 |
303 | "read": {
304 | "request": [],
305 | "response": "bytes"
306 | },
307 |
308 | "write": {
309 | "request": [ {"name": "data", "type": "bytes"} ],
310 | "response": "null"
311 | }
312 |
313 | }
314 |
315 | }
316 | DATUM
317 | ,
318 | <<<'DATUM'
319 | {
320 | "protocol" : "API",
321 | "namespace" : "xyz.api",
322 | "types" : [ {
323 | "type" : "enum",
324 | "name" : "Symbology",
325 | "namespace" : "xyz.api.product",
326 | "symbols" : [ "OPRA", "CUSIP", "ISIN", "SEDOL" ]
327 | }, {
328 | "type" : "record",
329 | "name" : "Symbol",
330 | "namespace" : "xyz.api.product",
331 | "fields" : [ {
332 | "name" : "symbology",
333 | "type" : "xyz.api.product.Symbology"
334 | }, {
335 | "name" : "symbol",
336 | "type" : "string"
337 | } ]
338 | }, {
339 | "type" : "record",
340 | "name" : "MultiSymbol",
341 | "namespace" : "xyz.api.product",
342 | "fields" : [ {
343 | "name" : "symbols",
344 | "type" : {
345 | "type" : "map",
346 | "values" : "xyz.api.product.Symbol"
347 | }
348 | } ]
349 | } ],
350 | "messages" : {
351 | }
352 | }
353 | DATUM
354 | );
355 | }
356 |
--------------------------------------------------------------------------------
/test/SchemaTest.php:
--------------------------------------------------------------------------------
1 | schema_string = $schema_string;
45 | $this->is_valid = $is_valid;
46 | $this->name = $name ? $name : $schema_string;
47 | $this->normalized_schema_string = $normalized_schema_string
48 | ? $normalized_schema_string : json_encode(json_decode($schema_string, true));
49 | $this->comment = $comment;
50 | }
51 | }
52 |
53 | /**
54 | * Class SchemaTest
55 | */
56 | class SchemaTest extends \PHPUnit\Framework\TestCase
57 | {
58 | static $examples = array();
59 | static $valid_examples = array();
60 |
61 | /**
62 | * @return array
63 | */
64 | protected static function make_primitive_examples()
65 | {
66 | $examples = array();
67 | foreach (array('null', 'boolean',
68 | 'int', 'long',
69 | 'float', 'double',
70 | 'bytes', 'string')
71 | as $type)
72 | {
73 | $examples []= new SchemaExample(sprintf('"%s"', $type), true, sprintf('{"type":"%s"}', $type));
74 | $examples []= new SchemaExample(sprintf('{"type": "%s"}', $type), true, sprintf('{"type":"%s"}', $type));
75 | }
76 | return $examples;
77 | }
78 |
79 | protected static function make_examples()
80 | {
81 | $primitive_examples = array_merge(array(new SchemaExample('"True"', false),
82 | new SchemaExample('{"no_type": "test"}', false),
83 | new SchemaExample('{"type": "panther"}', false)),
84 | self::make_primitive_examples());
85 |
86 | $array_examples = array(
87 | new SchemaExample('{"type": "array", "items": "long"}', true, '{"type":"array","items":{"type":"long"}}'),
88 | new SchemaExample('
89 | {"type": "array",
90 | "items": {"type": "enum", "name": "Test", "symbols": ["A", "B"]}}
91 | ', true, '{"type":"array","items":{"type":"enum","name":"Test","symbols":["A","B"]}}'));
92 |
93 | $map_examples = array(
94 | new SchemaExample('{"type": "map", "values": "long"}', true, '{"type":"map","values":{"type":"long"}}'),
95 | new SchemaExample('
96 | {"type": "map",
97 | "values": {"type": "enum", "name": "Test", "symbols": ["A", "B"]}}
98 | ', true, ));
99 |
100 | $union_examples = array(
101 | new SchemaExample('["string", "null", "long"]', true, '[{"type":"string"},{"type":"null"},{"type":"long"}]'),
102 | new SchemaExample('["null", "null"]', false),
103 | new SchemaExample('["long", "long"]', false),
104 | new SchemaExample('
105 | [{"type": "array", "items": "long"}
106 | {"type": "array", "items": "string"}]
107 | ', false),
108 | new SchemaExample('["long",
109 | {"type": "long"},
110 | "int"]', false),
111 | new SchemaExample('["long",
112 | {"type": "array", "items": "long"},
113 | {"type": "map", "values": "long"},
114 | "int"]', true, '[{"type":"long"},{"type":"array","items":{"type":"long"}},{"type":"map","values":{"type":"long"}},{"type":"int"}]'),
115 | new SchemaExample('["long",
116 | ["string", "null"],
117 | "int"]', false),
118 | new SchemaExample('["long",
119 | ["string", "null"],
120 | "int"]', false),
121 | new SchemaExample('["null", "boolean", "int", "long", "float", "double",
122 | "string", "bytes",
123 | {"type": "array", "items":"int"},
124 | {"type": "map", "values":"int"},
125 | {"name": "bar", "type":"record",
126 | "fields":[{"name":"label", "type":"string"}]},
127 | {"name": "foo", "type":"fixed",
128 | "size":16},
129 | {"name": "baz", "type":"enum", "symbols":["A", "B", "C"]}
130 | ]', true, '[{"type":"null"},{"type":"boolean"},{"type":"int"},{"type":"long"},{"type":"float"},{"type":"double"},{"type":"string"},{"type":"bytes"},{"type":"array","items":{"type":"int"}},{"type":"map","values":{"type":"int"}},{"type":"record","name":"bar","fields":[{"name":"label","type":{"type":"string"}}]},{"type":"fixed","name":"foo","size":16},{"type":"enum","name":"baz","symbols":["A","B","C"]}]'),
131 | new SchemaExample('
132 | [{"name":"subtract", "namespace":"com.example",
133 | "type":"record",
134 | "fields":[{"name":"minuend", "type":"int"},
135 | {"name":"subtrahend", "type":"int"}]},
136 | {"name": "divide", "namespace":"com.example",
137 | "type":"record",
138 | "fields":[{"name":"quotient", "type":"int"},
139 | {"name":"dividend", "type":"int"}]},
140 | {"type": "array", "items": "string"}]
141 | ', true, '[{"type":"record","name":"subtract","namespace":"com.example","fields":[{"name":"minuend","type":{"type":"int"}},{"name":"subtrahend","type":{"type":"int"}}]},{"type":"record","name":"divide","namespace":"com.example","fields":[{"name":"quotient","type":{"type":"int"}},{"name":"dividend","type":{"type":"int"}}]},{"type":"array","items":{"type":"string"}}]'),
142 | );
143 |
144 | $fixed_examples = array(
145 | new SchemaExample('{"type": "fixed", "name": "Test", "size": 1}', true),
146 | new SchemaExample('
147 | {"type": "fixed",
148 | "name": "MyFixed",
149 | "namespace": "org.apache.hadoop.avro",
150 | "size": 1}
151 | ', true),
152 | new SchemaExample('
153 | {"type": "fixed",
154 | "name": "Missing size"}
155 | ', false),
156 | new SchemaExample('
157 | {"type": "fixed",
158 | "size": 314}
159 | ', false),
160 | new SchemaExample('{"type":"fixed","name":"ex","doc":"this should be ignored","size": 314}',
161 | true,
162 | '{"type":"fixed","name":"ex","size":314}'),
163 | new SchemaExample('{"name": "bar",
164 | "namespace": "com.example",
165 | "type": "fixed",
166 | "size": 32 }', true,
167 | '{"type":"fixed","name":"bar","namespace":"com.example","size":32}'),
168 | new SchemaExample('{"name": "com.example.bar",
169 | "type": "fixed",
170 | "size": 32 }', true,
171 | '{"type":"fixed","name":"bar","namespace":"com.example","size":32}'));
172 |
173 | $fixed_examples []= new SchemaExample(
174 | '{"type":"fixed","name":"_x.bar","size":4}', true,
175 | '{"type":"fixed","name":"bar","namespace":"_x","size":4}');
176 | $fixed_examples []= new SchemaExample(
177 | '{"type":"fixed","name":"baz._x","size":4}', true,
178 | '{"type":"fixed","name":"_x","namespace":"baz","size":4}');
179 | $fixed_examples []= new SchemaExample(
180 | '{"type":"fixed","name":"baz.3x","size":4}', false);
181 |
182 | $enum_examples = array(
183 | new SchemaExample('{"type": "enum", "name": "Test", "symbols": ["A", "B"]}', true),
184 | new SchemaExample('
185 | {"type": "enum",
186 | "name": "Status",
187 | "symbols": "Normal Caution Critical"}
188 | ', false),
189 | new SchemaExample('
190 | {"type": "enum",
191 | "name": [ 0, 1, 1, 2, 3, 5, 8 ],
192 | "symbols": ["Golden", "Mean"]}
193 | ', false),
194 | new SchemaExample('
195 | {"type": "enum",
196 | "symbols" : ["I", "will", "fail", "no", "name"]}
197 | ', false),
198 | new SchemaExample('
199 | {"type": "enum",
200 | "name": "Test"
201 | "symbols" : ["AA", "AA"]}
202 | ', false),
203 | new SchemaExample('{"type":"enum","name":"Test","symbols":["AA", 16]}',
204 | false),
205 | new SchemaExample('
206 | {"type": "enum",
207 | "name": "blood_types",
208 | "doc": "AB is freaky.",
209 | "symbols" : ["A", "AB", "B", "O"]}
210 | ', true),
211 | new SchemaExample('
212 | {"type": "enum",
213 | "name": "blood-types",
214 | "doc": 16,
215 | "symbols" : ["A", "AB", "B", "O"]}
216 | ', false)
217 | );
218 |
219 |
220 | $record_examples = array();
221 | $record_examples []= new SchemaExample('
222 | {"type": "record",
223 | "name": "Test",
224 | "fields": [{"name": "f",
225 | "type": "long"}]}
226 | ', true, '{"type":"record","name":"Test","fields":[{"name":"f","type":{"type":"long"}}]}');
227 | $record_examples []= new SchemaExample('
228 | {"type": "error",
229 | "name": "Test",
230 | "fields": [{"name": "f",
231 | "type": "long"}]}
232 | ', true, '{"type":"error","name":"Test","fields":[{"name":"f","type":{"type":"long"}}]}');
233 | $record_examples []= new SchemaExample('
234 | {"type": "record",
235 | "name": "Node",
236 | "fields": [{"name": "label", "type": "string"},
237 | {"name": "children",
238 | "type": {"type": "array", "items": "Node"}}]}
239 | ', true, '{"type":"record","name":"Node","fields":[{"name":"label","type":{"type":"string"}},{"name":"children","type":{"type":"array","items":"Node"}}]}');
240 | $record_examples []= new SchemaExample('
241 | {"type": "record",
242 | "name": "ListLink",
243 | "fields": [{"name": "car", "type": "int"},
244 | {"name": "cdr", "type": "ListLink"}]}
245 | ', true, '{"type":"record","name":"ListLink","fields":[{"name":"car","type":{"type":"int"}},{"name":"cdr","type":"ListLink"}]}');
246 | $record_examples []= new SchemaExample('
247 | {"type": "record",
248 | "name": "Lisp",
249 | "fields": [{"name": "value",
250 | "type": ["null", "string"]}]}
251 | ', true, '{"type":"record","name":"Lisp","fields":[{"name":"value","type":[{"type":"null"},{"type":"string"}]}]}');
252 | $record_examples []= new SchemaExample('
253 | {"type": "record",
254 | "name": "Lisp",
255 | "fields": [{"name": "value",
256 | "type": ["null", "string",
257 | {"type": "record",
258 | "name": "Cons",
259 | "fields": [{"name": "car", "type": "string"},
260 | {"name": "cdr", "type": "string"}]}]}]}
261 | ', true, '{"type":"record","name":"Lisp","fields":[{"name":"value","type":[{"type":"null"},{"type":"string"},{"type":"record","name":"Cons","fields":[{"name":"car","type":{"type":"string"}},{"name":"cdr","type":{"type":"string"}}]}]}]}');
262 | $record_examples []= new SchemaExample('
263 | {"type": "record",
264 | "name": "Lisp",
265 | "fields": [{"name": "value",
266 | "type": ["null", "string",
267 | {"type": "record",
268 | "name": "Cons",
269 | "fields": [{"name": "car", "type": "Lisp"},
270 | {"name": "cdr", "type": "Lisp"}]}]}]}
271 | ', true, '{"type":"record","name":"Lisp","fields":[{"name":"value","type":[{"type":"null"},{"type":"string"},{"type":"record","name":"Cons","fields":[{"name":"car","type":"Lisp"},{"name":"cdr","type":"Lisp"}]}]}]}');
272 | $record_examples []= new SchemaExample('
273 | {"type": "record",
274 | "name": "HandshakeRequest",
275 | "namespace": "org.apache.avro.ipc",
276 | "fields": [{"name": "clientHash",
277 | "type": {"type": "fixed", "name": "MD5", "size": 16}},
278 | {"name": "meta",
279 | "type": ["null", {"type": "map", "values": "bytes"}]}]}
280 | ', true, '{"type":"record","name":"HandshakeRequest","namespace":"org.apache.avro.ipc","fields":[{"name":"clientHash","type":{"type":"fixed","name":"MD5","size":16}},{"name":"meta","type":[{"type":"null"},{"type":"map","values":{"type":"bytes"}}]}]}');
281 | $record_examples []= new SchemaExample('
282 | {"type": "record",
283 | "name": "HandshakeRequest",
284 | "namespace": "org.apache.avro.ipc",
285 | "fields": [{"name": "clientHash",
286 | "type": {"type": "fixed", "name": "MD5", "size": 16}},
287 | {"name": "clientProtocol", "type": ["null", "string"]},
288 | {"name": "serverHash", "type": "MD5"},
289 | {"name": "meta",
290 | "type": ["null", {"type": "map", "values": "bytes"}]}]}
291 | ', true, '{"type":"record","name":"HandshakeRequest","namespace":"org.apache.avro.ipc","fields":[{"name":"clientHash","type":{"type":"fixed","name":"MD5","size":16}},{"name":"clientProtocol","type":[{"type":"null"},{"type":"string"}]},{"name":"serverHash","type":"MD5"},{"name":"meta","type":[{"type":"null"},{"type":"map","values":{"type":"bytes"}}]}]}');
292 | $record_examples []= new SchemaExample('
293 | {"type": "record",
294 | "name": "HandshakeResponse",
295 | "namespace": "org.apache.avro.ipc",
296 | "fields": [{"name": "match",
297 | "type": {"type": "enum",
298 | "name": "HandshakeMatch",
299 | "symbols": ["BOTH", "CLIENT", "NONE"]}},
300 | {"name": "serverProtocol", "type": ["null", "string"]},
301 | {"name": "serverHash",
302 | "type": ["null",
303 | {"name": "MD5", "size": 16, "type": "fixed"}]},
304 | {"name": "meta",
305 | "type": ["null", {"type": "map", "values": "bytes"}]}]}
306 | ', true,
307 | '{"type":"record","name":"HandshakeResponse","namespace":"org.apache.avro.ipc","fields":[{"name":"match","type":{"type":"enum","name":"HandshakeMatch","symbols":["BOTH","CLIENT","NONE"]}},{"name":"serverProtocol","type":[{"type":"null"},{"type":"string"}]},{"name":"serverHash","type":[{"type":"null"},{"type":"fixed","name":"MD5","size":16}]},{"name":"meta","type":[{"type":"null"},{"type":"map","values":{"type":"bytes"}}]}]}'
308 | );
309 | $record_examples []= new SchemaExample('{"type": "record",
310 | "namespace": "org.apache.avro",
311 | "name": "Interop",
312 | "fields": [{"type": {"fields": [{"type": {"items": "org.apache.avro.Node",
313 | "type": "array"},
314 | "name": "children"}],
315 | "type": "record",
316 | "name": "Node"},
317 | "name": "recordField"}]}
318 | ', true, '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"children","type":{"type":"array","items":"Node"}}]}}]}');
319 | $record_examples [] = new SchemaExample('{"type": "record",
320 | "namespace": "org.apache.avro",
321 | "name": "Interop",
322 | "fields": [{"type": {"symbols": ["A", "B", "C"], "type": "enum", "name": "Kind"},
323 | "name": "enumField"},
324 | {"type": {"fields": [{"type": "string", "name": "label"},
325 | {"type": {"items": "org.apache.avro.Node", "type": "array"},
326 | "name": "children"}],
327 | "type": "record",
328 | "name": "Node"},
329 | "name": "recordField"}]}', true, '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":{"type":"string"}},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}');
330 |
331 | $record_examples []= new SchemaExample('
332 | {"type": "record",
333 | "name": "Interop",
334 | "namespace": "org.apache.avro",
335 | "fields": [{"name": "intField", "type": "int"},
336 | {"name": "longField", "type": "long"},
337 | {"name": "stringField", "type": "string"},
338 | {"name": "boolField", "type": "boolean"},
339 | {"name": "floatField", "type": "float"},
340 | {"name": "doubleField", "type": "double"},
341 | {"name": "bytesField", "type": "bytes"},
342 | {"name": "nullField", "type": "null"},
343 | {"name": "arrayField",
344 | "type": {"type": "array", "items": "double"}},
345 | {"name": "mapField",
346 | "type": {"type": "map",
347 | "values": {"name": "Foo",
348 | "type": "record",
349 | "fields": [{"name": "label",
350 | "type": "string"}]}}},
351 | {"name": "unionField",
352 | "type": ["boolean",
353 | "double",
354 | {"type": "array", "items": "bytes"}]},
355 | {"name": "enumField",
356 | "type": {"type": "enum",
357 | "name": "Kind",
358 | "symbols": ["A", "B", "C"]}},
359 | {"name": "fixedField",
360 | "type": {"type": "fixed", "name": "MD5", "size": 16}},
361 | {"name": "recordField",
362 | "type": {"type": "record",
363 | "name": "Node",
364 | "fields": [{"name": "label", "type": "string"},
365 | {"name": "children",
366 | "type": {"type": "array",
367 | "items": "Node"}}]}}]}
368 | ', true,
369 | '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"intField","type":{"type":"int"}},{"name":"longField","type":{"type":"long"}},{"name":"stringField","type":{"type":"string"}},{"name":"boolField","type":{"type":"boolean"}},{"name":"floatField","type":{"type":"float"}},{"name":"doubleField","type":{"type":"double"}},{"name":"bytesField","type":{"type":"bytes"}},{"name":"nullField","type":{"type":"null"}},{"name":"arrayField","type":{"type":"array","items":{"type":"double"}}},{"name":"mapField","type":{"type":"map","values":{"type":"record","name":"Foo","fields":[{"name":"label","type":{"type":"string"}}]}}},{"name":"unionField","type":[{"type":"boolean"},{"type":"double"},{"type":"array","items":{"type":"bytes"}}]},{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"fixedField","type":{"type":"fixed","name":"MD5","size":16}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":{"type":"string"}},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}');
370 | $record_examples []= new SchemaExample('{"type": "record", "namespace": "org.apache.avro", "name": "Interop", "fields": [{"type": "int", "name": "intField"}, {"type": "long", "name": "longField"}, {"type": "string", "name": "stringField"}, {"type": "boolean", "name": "boolField"}, {"type": "float", "name": "floatField"}, {"type": "double", "name": "doubleField"}, {"type": "bytes", "name": "bytesField"}, {"type": "null", "name": "nullField"}, {"type": {"items": "double", "type": "array"}, "name": "arrayField"}, {"type": {"type": "map", "values": {"fields": [{"type": "string", "name": "label"}], "type": "record", "name": "Foo"}}, "name": "mapField"}, {"type": ["boolean", "double", {"items": "bytes", "type": "array"}], "name": "unionField"}, {"type": {"symbols": ["A", "B", "C"], "type": "enum", "name": "Kind"}, "name": "enumField"}, {"type": {"type": "fixed", "name": "MD5", "size": 16}, "name": "fixedField"}, {"type": {"fields": [{"type": "string", "name": "label"}, {"type": {"items": "org.apache.avro.Node", "type": "array"}, "name": "children"}], "type": "record", "name": "Node"}, "name": "recordField"}]}
371 | ', true, '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"intField","type":{"type":"int"}},{"name":"longField","type":{"type":"long"}},{"name":"stringField","type":{"type":"string"}},{"name":"boolField","type":{"type":"boolean"}},{"name":"floatField","type":{"type":"float"}},{"name":"doubleField","type":{"type":"double"}},{"name":"bytesField","type":{"type":"bytes"}},{"name":"nullField","type":{"type":"null"}},{"name":"arrayField","type":{"type":"array","items":{"type":"double"}}},{"name":"mapField","type":{"type":"map","values":{"type":"record","name":"Foo","fields":[{"name":"label","type":{"type":"string"}}]}}},{"name":"unionField","type":[{"type":"boolean"},{"type":"double"},{"type":"array","items":{"type":"bytes"}}]},{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"fixedField","type":{"type":"fixed","name":"MD5","size":16}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":{"type":"string"}},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}');
372 | $record_examples []= new SchemaExample('
373 | {"type": "record",
374 | "name": "ipAddr",
375 | "fields": [{"name": "addr",
376 | "type": [{"name": "IPv6", "type": "fixed", "size": 16},
377 | {"name": "IPv4", "type": "fixed", "size": 4}]}]}
378 | ', true,
379 | '{"type":"record","name":"ipAddr","fields":[{"name":"addr","type":[{"type":"fixed","name":"IPv6","size":16},{"type":"fixed","name":"IPv4","size":4}]}]}');
380 | $record_examples []= new SchemaExample('
381 | {"type": "record",
382 | "name": "Address",
383 | "fields": [{"type": "string"},
384 | {"type": "string", "name": "City"}]}
385 | ', false);
386 | $record_examples []= new SchemaExample('
387 | {"type": "record",
388 | "name": "Event",
389 | "fields": [{"name": "Sponsor"},
390 | {"name": "City", "type": "string"}]}
391 | ', false);
392 | $record_examples []= new SchemaExample('
393 | {"type": "record",
394 | "fields": "His vision, from the constantly passing bars,"
395 | "name", "Rainer"}
396 | ', false);
397 | $record_examples []= new SchemaExample('
398 | {"name": ["Tom", "Jerry"],
399 | "type": "record",
400 | "fields": [{"name": "name", "type": "string"}]}
401 | ', false);
402 | $record_examples []= new SchemaExample('
403 | {"type":"record","name":"foo","doc":"doc string",
404 | "fields":[{"name":"bar", "type":"int", "order":"ascending", "default":1}]}
405 | ',
406 | true,
407 | '{"type":"record","name":"foo","doc":"doc string","fields":[{"name":"bar","type":{"type":"int"},"default":1,"order":"ascending"}]}');
408 | $record_examples []= new SchemaExample('
409 | {"type":"record", "name":"foo", "doc":"doc string",
410 | "fields":[{"name":"bar", "type":"int", "order":"bad"}]}
411 | ', false);
412 | // `"default":null` should not be lost in `to_avro`.
413 | $record_examples []= new SchemaExample(
414 | '{"type":"record","name":"foo","fields":[{"name":"bar","type":["null","string"],"default":null}]}',
415 | true,
416 | '{"type":"record","name":"foo","fields":[{"name":"bar","type":[{"type":"null"},{"type":"string"}],"default":null}]}');
417 | // Don't lose the "doc" attributes of record fields.
418 | $record_examples []= new SchemaExample(
419 | '{"type":"record","name":"foo","fields":[{"name":"bar","type":["null","string"],"doc":"Bar name."}]}',
420 | true,
421 | '{"type":"record","name":"foo","fields":[{"name":"bar","type":[{"type":"null"},{"type":"string"}],"doc":"Bar name."}]}');
422 |
423 | $primitive_examples []= new SchemaExample(
424 | '{ "type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2 }',
425 | true
426 | );
427 | $fixed_examples []= new SchemaExample(
428 | '{ "type": "fixed", "size": 32, "name": "hash", "logicalType": "md5" }',
429 | true,
430 | '{"type":"fixed","name":"hash","logicalType":"md5","size":32}'
431 | );
432 | $enum_examples []= new SchemaExample(
433 | '{"type": "enum", "logicalType": "foo", "name": "foo", "symbols": ["FOO", "BAR"], "foo": "bar"}',
434 | true,
435 | '{"type":"enum","name":"foo","logicalType":"foo","foo":"bar","symbols":["FOO","BAR"]}'
436 | );
437 | $array_examples []= new SchemaExample(
438 | '{"type": "array", "logicalType": "foo", "items": "string", "foo": "bar"}',
439 | true,
440 | '{"type":"array","items":{"type":"string"},"logicalType":"foo","foo":"bar"}'
441 | );
442 | $map_examples []= new SchemaExample(
443 | '{"type": "map", "logicalType": "foo", "values": "long", "foo": "bar"}',
444 | true,
445 | '{"type":"map","values":{"type":"long"},"logicalType":"foo","foo":"bar"}'
446 | );
447 | $record_examples []= new SchemaExample(
448 | '{ "type": "record", "name": "foo", "logicalType": "bar", "fields": [], "foo": "bar" }',
449 | true,
450 | '{"type":"record","name":"foo","logicalType":"bar","foo":"bar","fields":[]}'
451 | );
452 |
453 | self::$examples = array_merge($primitive_examples,
454 | $fixed_examples,
455 | $enum_examples,
456 | $array_examples,
457 | $map_examples,
458 | $union_examples,
459 | $record_examples);
460 | self::$valid_examples = array();
461 | foreach (self::$examples as $example)
462 | {
463 | if ($example->is_valid)
464 | self::$valid_examples []= $example;
465 | }
466 | }
467 |
468 | function test_json_decode()
469 | {
470 | $this->assertEquals(json_decode('null', true), null);
471 | $this->assertEquals(json_decode('32', true), 32);
472 | $this->assertEquals(json_decode('"32"', true), '32');
473 | $this->assertEquals((array) json_decode('{"foo": 27}'), array("foo" => 27));
474 | $this->assertTrue(is_array(json_decode('{"foo": 27}', true)));
475 | $this->assertEquals(json_decode('{"foo": 27}', true), array("foo" => 27));
476 | $this->assertEquals(json_decode('["bar", "baz", "blurfl"]', true),
477 | array("bar", "baz", "blurfl"));
478 | $this->assertFalse(is_array(json_decode('null', true)));
479 | $this->assertEquals(json_decode('{"type": "null"}', true), array("type" => 'null'));
480 | $this->assertEquals(json_decode('"boolean"'), 'boolean');
481 | }
482 |
483 | function parse_bad_json_provider()
484 | {
485 | return array(
486 | // Valid
487 | array('{"type": "array", "items": "long"}', null),
488 | // Trailing comma
489 | array('{"type": "array", "items": "long", }', "JSON decode error 4: Syntax error"),
490 | // Wrong quotes
491 | array("{'type': 'array', 'items': 'long'}", "JSON decode error 4: Syntax error"),
492 | // Binary data
493 | array("\x11\x07", "JSON decode error 3: Control character error, possibly incorrectly encoded"),
494 | );
495 | }
496 |
497 | /**
498 | * @dataProvider parse_bad_json_provider
499 | */
500 | function test_parse_bad_json($json, $failure)
501 | {
502 | if (defined('HHVM_VERSION'))
503 | {
504 | // Under HHVM, json_decode is not as strict and feature complete as standard PHP.
505 | $this->markTestSkipped();
506 | }
507 | try
508 | {
509 | $schema = AvroSchema::parse($json);
510 | $this->assertEquals($failure, null);
511 | }
512 | catch (AvroSchemaParseException $e)
513 | {
514 | $this->assertEquals($failure, $e->getMessage());
515 | }
516 | }
517 |
518 | /**
519 | * @return array
520 | */
521 | function schema_examples_provider()
522 | {
523 | self::make_examples();
524 | $ary = array();
525 | foreach (self::$examples as $example)
526 | $ary []= array($example);
527 | return $ary;
528 | }
529 |
530 | /**
531 | * @dataProvider schema_examples_provider
532 | * @param $example
533 | */
534 | function test_parse($example)
535 | {
536 | $schema_string = $example->schema_string;
537 | try
538 | {
539 | $normalized_schema_string = $example->normalized_schema_string;
540 | $schema = AvroSchema::parse($schema_string);
541 | $this->assertTrue($example->is_valid,
542 | sprintf("schema_string: %s\n",
543 | $schema_string));
544 | // strval() roughly does to_avro() + json_encode()
545 | $this->assertEquals($normalized_schema_string, strval($schema));
546 | }
547 | catch (AvroSchemaParseException $e)
548 | {
549 | $this->assertFalse($example->is_valid,
550 | sprintf("schema_string: %s\n%s",
551 | $schema_string,
552 | $e->getMessage()));
553 | }
554 | }
555 |
556 | function test_record_doc()
557 | {
558 | $json = '{"type": "record", "name": "foo", "doc": "Foo doc.",
559 | "fields": [{"name": "bar", "type": "int", "doc": "Bar doc."}]}';
560 | $schema = AvroSchema::parse($json);
561 | $this->assertEquals($schema->doc(), "Foo doc.");
562 | $fields = $schema->fields();
563 | $this->assertCount(1, $fields);
564 | $bar = $fields[0];
565 | $this->assertEquals($bar->doc(), "Bar doc.");
566 | }
567 |
568 | function test_enum_doc()
569 | {
570 | $json = '{"type": "enum", "name": "blood_types", "doc": "AB is freaky.", "symbols": ["A", "AB", "B", "O"]}';
571 | $schema = AvroSchema::parse($json);
572 | $this->assertEquals($schema->doc(), "AB is freaky.");
573 | }
574 |
575 | function test_enum_default()
576 | {
577 | $json = '{"type": "enum", "name": "blood_types", "symbols": ["A", "AB", "B", "O"]}';
578 | $schema = AvroSchema::parse($json);
579 |
580 | $this->assertEquals(null, $schema->default_value());
581 | $this->assertEquals(false, $schema->has_default_value());
582 |
583 |
584 | $json = '{"type": "enum", "name": "blood_types", "default": "AB", "symbols": ["A", "AB", "B", "O"]}';
585 | $schema = AvroSchema::parse($json);
586 |
587 | $this->assertEquals([
588 | 'type' => 'enum',
589 | 'name' => 'blood_types',
590 | 'default' => 'AB',
591 | 'symbols' => ["A", "AB", "B", "O"],
592 | ], $schema->to_avro());
593 |
594 | $this->assertEquals('AB', $schema->default_value());
595 | $this->assertEquals(true, $schema->has_default_value());
596 | }
597 |
598 | function test_logical_type()
599 | {
600 | $json = '{ "type": "bytes", "logicalType": "decimal", "precision": 4, "scale": 2 }';
601 | $schema = AvroSchema::parse($json);
602 | $this->assertEquals($schema->logical_type(), "decimal");
603 | $this->assertEquals($schema->extra_attributes(), ["precision" => 4, "scale" => 2]);
604 | }
605 | }
606 |
--------------------------------------------------------------------------------
/test/StringIOTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(0, $strio->tell());
32 | $str = 'foo';
33 | $strlen = strlen($str);
34 | $this->assertEquals($strlen, $strio->write($str));
35 | $this->assertEquals($strlen, $strio->tell());
36 | }
37 |
38 | public function test_seek()
39 | {
40 | $this->markTestIncomplete('This test has not been implemented yet.');
41 | }
42 |
43 | public function test_tell()
44 | {
45 | $this->markTestIncomplete('This test has not been implemented yet.');
46 | }
47 |
48 | public function test_read()
49 | {
50 | $this->markTestIncomplete('This test has not been implemented yet.');
51 | }
52 |
53 | public function test_string_rep()
54 | {
55 | $writers_schema_json = '"null"';
56 | $writers_schema = AvroSchema::parse($writers_schema_json);
57 | $datum_writer = new AvroIODatumWriter($writers_schema);
58 | $strio = new AvroStringIO();
59 | $this->assertEquals('', $strio->string());
60 | $dw = new AvroDataIOWriter($strio, $datum_writer, $writers_schema_json);
61 | $dw->close();
62 |
63 | $this->assertEquals(57, strlen($strio->string()),
64 | AvroDebug::ascii_string($strio->string()));
65 |
66 | $read_strio = new AvroStringIO($strio->string());
67 |
68 | $datum_reader = new AvroIODatumReader();
69 | $dr = new AvroDataIOReader($read_strio, $datum_reader);
70 | $read_data = $dr->data();
71 | $datum_count = count($read_data);
72 | $this->assertEquals(0, $datum_count);
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/test/data/user.avsc:
--------------------------------------------------------------------------------
1 | {"type": "record", "namespace": "example.avro", "name": "User", "fields": [{"type": "string", "name": "name"}, {"type": ["string", "null"], "name": "favorite_color"}, {"type": {"items": "int", "type": "array"}, "name": "favorite_numbers"}]}
2 |
--------------------------------------------------------------------------------
/test/data/users.avro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flix-tech/avro-php/1d537bfe4ecab405917d15eee8f2020743fc8596/test/data/users.avro
--------------------------------------------------------------------------------
/test/data/users.json:
--------------------------------------------------------------------------------
1 | {"name":"Alyssa","favorite_color":null,"favorite_numbers":[3,9,15,20]}
2 |
--------------------------------------------------------------------------------
/test/data/users_deflate.avro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flix-tech/avro-php/1d537bfe4ecab405917d15eee8f2020743fc8596/test/data/users_deflate.avro
--------------------------------------------------------------------------------
/test/data/users_snappy.avro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flix-tech/avro-php/1d537bfe4ecab405917d15eee8f2020743fc8596/test/data/users_snappy.avro
--------------------------------------------------------------------------------
/test/test_helper.php:
--------------------------------------------------------------------------------
1 |