You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by cu...@apache.org on 2010/08/30 18:50:41 UTC

svn commit: r990860 [2/3] - in /avro/trunk: ./ lang/php/ lang/php/examples/ lang/php/lib/ lang/php/lib/avro/ lang/php/test/

Added: avro/trunk/lang/php/lib/avro/schema.php
URL: http://svn.apache.org/viewvc/avro/trunk/lang/php/lib/avro/schema.php?rev=990860&view=auto
==============================================================================
--- avro/trunk/lang/php/lib/avro/schema.php (added)
+++ avro/trunk/lang/php/lib/avro/schema.php Mon Aug 30 16:50:40 2010
@@ -0,0 +1,1450 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Avro Schema and and Avro Schema support classes.
+ * @package Avro
+ */
+
+/** TODO
+ * - ARRAY have only type and item attributes (what about metadata?)
+ * - MAP keys are (assumed?) to be strings
+ * - FIXED size must be integer (must be positive? less than MAXINT?)
+ * - primitive type names cannot have a namespace (so throw an error? or ignore?)
+ * - schema may contain multiple definitions of a named schema
+ *   if definitions are equivalent (?)
+ *  - Cleanup default namespace and named schemata handling.
+ *     - For one, it appears to be *too* global. According to the spec,
+ *       we should only be referencing schemas that are named within the
+ *       *enclosing* schema, so those in sibling schemas (say, unions or fields)
+ *       shouldn't be referenced, if I understand the spec correctly.
+ *     - Also, if a named schema is defined more than once in the same schema,
+ *       it must have the same definition: so it appears we *do* need to keep
+ *       track of named schemata globally as well. (And does this play well
+ *       with the requirements regarding enclosing schema?
+ *  - default values for bytes and fixed fields are JSON strings,
+ *    where unicode code points 0-255 are mapped to unsigned 8-bit byte values 0-255
+ *  - make sure other default values for other schema are of appropriate type
+ *  - Should AvroField really be an AvroSchema object? Avro Fields have a name
+ *    attribute, but not a namespace attribute (and the name can't be namespace
+ *    qualified). It also has additional attributes such as doc, which named schemas
+ *    enum and record have (though not fixed schemas, which also have names), and
+ *    fields also have default and order attributes, shared by no other schema type.
+ */
+
+/**
+ * Exceptions associated with parsing JSON schema represenations
+ * @package Avro
+ */
+class AvroSchemaParseException extends AvroException {};
+
+/**
+ * @package Avro
+ */
+class AvroSchema
+{
+  /**
+   * @var int lower bound of integer values: -(1 << 31)
+   */
+  const INT_MIN_VALUE = -2147483648;
+
+  /**
+   * @var int upper bound of integer values: (1 << 31) - 1
+   */
+  const INT_MAX_VALUE = 2147483647;
+
+  /**
+   * @var long lower bound of long values: -(1 << 63)
+   */
+  const LONG_MIN_VALUE = -9223372036854775808;
+
+  /**
+   * @var long upper bound of long values: (1 << 63) - 1
+   */
+  const LONG_MAX_VALUE =  9223372036854775807;
+
+  /**
+   * @var string null schema type name
+   */
+  const NULL_TYPE = 'null';
+
+  /**
+   * @var string boolean schema type name
+   */
+  const BOOLEAN_TYPE = 'boolean';
+
+  /**
+   * int schema type value is a 32-bit signed int
+   * @var string int schema type name.
+   */
+  const INT_TYPE = 'int';
+
+  /**
+   * long schema type value is a 64-bit signed int
+   * @var string long schema type name
+   */
+  const LONG_TYPE = 'long';
+
+  /**
+   * float schema type value is a 32-bit IEEE 754 floating-point number
+   * @var string float schema type name
+   */
+  const FLOAT_TYPE = 'float';
+
+  /**
+   * double schema type value is a 64-bit IEEE 754 floating-point number
+   * @var string double schema type name
+   */
+  const DOUBLE_TYPE = 'double';
+
+  /**
+   * string schema type value is a Unicode character sequence
+   * @var string string schema type name
+   */
+  const STRING_TYPE = 'string';
+
+  /**
+   * bytes schema type value is a sequence of 8-bit unsigned bytes
+   * @var string bytes schema type name
+   */
+  const BYTES_TYPE = 'bytes';
+
+  // Complex Types
+  // Unnamed Schema
+  /**
+   * @var string array schema type name
+   */
+  const ARRAY_SCHEMA = 'array';
+
+  /**
+   * @var string map schema type name
+   */
+  const MAP_SCHEMA = 'map';
+
+  /**
+   * @var string union schema type name
+   */
+  const UNION_SCHEMA = 'union';
+
+  /**
+   * Unions of error schemas are used by Avro messages
+   * @var string error_union schema type name
+   */
+  const ERROR_UNION_SCHEMA = 'error_union';
+
+  // Named Schema
+
+  /**
+   * @var string enum schema type name
+   */
+  const ENUM_SCHEMA = 'enum';
+
+  /**
+   * @var string fixed schema type name
+   */
+  const FIXED_SCHEMA = 'fixed';
+
+  /**
+   * @var string record schema type name
+   */
+  const RECORD_SCHEMA = 'record';
+  // Other Schema
+
+  /**
+   * @var string error schema type name
+   */
+  const ERROR_SCHEMA = 'error';
+
+  /**
+   * @var string request schema type name
+   */
+  const REQUEST_SCHEMA = 'request';
+
+
+  // Schema attribute names
+  /**
+   * @var string schema type name attribute name
+   */
+  const TYPE_ATTR = 'type';
+
+  /**
+   * @var string named schema name attribute name
+   */
+  const NAME_ATTR = 'name';
+
+  /**
+   * @var string named schema namespace attribute name
+   */
+  const NAMESPACE_ATTR = 'namespace';
+
+  /**
+   * @var string derived attribute: doesn't appear in schema
+   */
+  const FULLNAME_ATTR = 'fullname';
+
+  /**
+   * @var string array schema size attribute name
+   */
+  const SIZE_ATTR = 'size';
+
+  /**
+   * @var string record fields attribute name
+   */
+  const FIELDS_ATTR = 'fields';
+
+  /**
+   * @var string array schema items attribute name
+   */
+  const ITEMS_ATTR = 'items';
+
+  /**
+   * @var string enum schema symbols attribute name
+   */
+  const SYMBOLS_ATTR = 'symbols';
+
+  /**
+   * @var string map schema values attribute name
+   */
+  const VALUES_ATTR = 'values';
+
+  /**
+   * @var string document string attribute name
+   */
+  const DOC_ATTR = 'doc';
+
+  /**
+   * @var array list of primitive schema type names
+   */
+  private static $primitive_types = array(self::NULL_TYPE, self::BOOLEAN_TYPE,
+                                          self::STRING_TYPE, self::BYTES_TYPE,
+                                          self::INT_TYPE, self::LONG_TYPE,
+                                          self::FLOAT_TYPE, self::DOUBLE_TYPE);
+
+  /**
+   * @var array list of named schema type names
+   */
+  private static $named_types = array(self::FIXED_SCHEMA, self::ENUM_SCHEMA,
+                                      self::RECORD_SCHEMA, self::ERROR_SCHEMA);
+
+  /**
+   * @param string $type a schema type name
+   * @returns boolean true if the given type name is a named schema type name
+   *                  and false otherwise.
+   */
+  public static function is_named_type($type)
+  {
+    return in_array($type, self::$named_types);
+  }
+
+  /**
+   * @param string $type a schema type name
+   * @returns boolean true if the given type name is a primitive schema type
+   *                  name and false otherwise.
+   */
+  public static function is_primitive_type($type)
+  {
+    return in_array($type, self::$primitive_types);
+  }
+
+  /**
+   * @param string $type a schema type name
+   * @returns boolean true if the given type name is a valid schema type
+   *                  name and false otherwise.
+   */
+  public static function is_valid_type($type)
+  {
+    return (self::is_primitive_type($type)
+            || self::is_named_type($type)
+            || in_array($type, array(self::ARRAY_SCHEMA,
+                                     self::MAP_SCHEMA,
+                                     self::UNION_SCHEMA,
+                                     self::REQUEST_SCHEMA,
+                                     self::ERROR_UNION_SCHEMA)));
+  }
+
+  /**
+   * @var array list of names of reserved attributes
+   */
+  private static $reserved_attrs = array(self::TYPE_ATTR,
+                                         self::NAME_ATTR,
+                                         self::NAMESPACE_ATTR,
+                                         self::FIELDS_ATTR,
+                                         self::ITEMS_ATTR,
+                                         self::SIZE_ATTR,
+                                         self::SYMBOLS_ATTR,
+                                         self::VALUES_ATTR);
+
+  /**
+   * @param string $json JSON-encoded schema
+   * @uses self::real_parse()
+   * @returns AvroSchema
+   */
+  public static function parse($json)
+  {
+    $schemata = new AvroNamedSchemata();
+    return self::real_parse(json_decode($json, true), null, $schemata);
+  }
+
+  /**
+   * @param mixed $avro JSON-decoded schema
+   * @param string $default_namespace namespace of enclosing schema
+   * @param AvroNamedSchemata &$schemata reference to named schemas
+   * @returns AvroSchema
+   * @throws AvroSchemaParseException
+   */
+  static function real_parse($avro, $default_namespace=null, &$schemata=null)
+  {
+    if (is_null($schemata))
+      $schemata = new AvroNamedSchemata();
+
+    if (is_array($avro))
+    {
+      $type = AvroUtil::array_value($avro, self::TYPE_ATTR);
+
+      if (self::is_primitive_type($type))
+        return new AvroPrimitiveSchema($type);
+
+      elseif (self::is_named_type($type))
+      {
+        $name = AvroUtil::array_value($avro, self::NAME_ATTR);
+        $namespace = AvroUtil::array_value($avro, self::NAMESPACE_ATTR);
+        $new_name = new AvroName($name, $namespace, $default_namespace);
+        $doc = AvroUtil::array_value($avro, self::DOC_ATTR);
+        switch ($type)
+        {
+          case self::FIXED_SCHEMA:
+            $size = AvroUtil::array_value($avro, self::SIZE_ATTR);
+            return new AvroFixedSchema($new_name, $doc,
+                                       $size,
+                                       $schemata);
+          case self::ENUM_SCHEMA:
+            $symbols = AvroUtil::array_value($avro, self::SYMBOLS_ATTR);
+            return new AvroEnumSchema($new_name, $doc,
+                                      $symbols,
+                                      $schemata);
+          case self::RECORD_SCHEMA:
+          case self::ERROR_SCHEMA:
+            $fields = AvroUtil::array_value($avro, self::FIELDS_ATTR);
+            return new AvroRecordSchema($new_name, $doc,
+                                        $fields,
+                                        $schemata, $type);
+          default:
+            throw new AvroSchemaParseException(
+              sprintf('Unknown named type: %s', $type));
+        }
+      }
+      elseif (self::is_valid_type($type))
+      {
+        switch ($type)
+        {
+          case self::ARRAY_SCHEMA:
+            return new AvroArraySchema($avro[self::ITEMS_ATTR],
+                                       $default_namespace,
+                                       $schemata);
+          case self::MAP_SCHEMA:
+            return new AvroMapSchema($avro[self::VALUES_ATTR],
+                                     $default_namespace,
+                                     $schemata);
+          default:
+            throw new AvroSchemaParseException(
+              sprintf('Unknown valid type: %s', $type));
+        }
+      }
+      elseif (!array_key_exists(self::TYPE_ATTR, $avro)
+              && AvroUtil::is_list($avro))
+        return new AvroUnionSchema($avro, $default_namespace, $schemata);
+      else
+        throw new AvroSchemaParseException(sprintf('Undefined type: %s',
+                                                   $type));
+    }
+    elseif (self::is_primitive_type($avro))
+      return new AvroPrimitiveSchema($avro);
+    else
+      throw new AvroSchemaParseException(
+        sprintf('%s is not a schema we know about.',
+                print_r($avro, true)));
+  }
+
+  /**
+   * @returns boolean true if $datum is valid for $expected_schema
+   *                  and false otherwise.
+   * @throws AvroSchemaParseException
+   */
+  public static function is_valid_datum($expected_schema, $datum)
+  {
+    switch($expected_schema->type)
+    {
+      case self::NULL_TYPE:
+        return is_null($datum);
+      case self::BOOLEAN_TYPE:
+        return is_bool($datum);
+      case self::STRING_TYPE:
+      case self::BYTES_TYPE:
+        return is_string($datum);
+      case self::INT_TYPE:
+        return (is_int($datum)
+                && (self::INT_MIN_VALUE <= $datum)
+                && ($datum <= self::INT_MAX_VALUE));
+      case self::LONG_TYPE:
+        return (is_int($datum)
+                && (self::LONG_MIN_VALUE <= $datum)
+                && ($datum <= self::LONG_MAX_VALUE));
+      case self::FLOAT_TYPE:
+      case self::DOUBLE_TYPE:
+        return (is_float($datum) || is_int($datum));
+      case self::ARRAY_SCHEMA:
+        if (is_array($datum))
+        {
+          foreach ($datum as $d)
+            if (!self::is_valid_datum($expected_schema->items(), $d))
+              return false;
+          return true;
+        }
+        return false;
+      case self::MAP_SCHEMA:
+        if (is_array($datum))
+        {
+          foreach ($datum as $k => $v)
+            if (!is_string($k)
+                || !self::is_valid_datum($expected_schema->values(), $v))
+              return false;
+          return true;
+        }
+        return false;
+      case self::UNION_SCHEMA:
+        foreach ($expected_schema->schemas() as $schema)
+          if (self::is_valid_datum($schema, $datum))
+            return true;
+        return false;
+      case self::ENUM_SCHEMA:
+        return in_array($datum, $expected_schema->symbols());
+      case self::FIXED_SCHEMA:
+        return (is_string($datum)
+                && (strlen($datum) == $expected_schema->size()));
+      case self::RECORD_SCHEMA:
+      case self::ERROR_SCHEMA:
+      case self::REQUEST_SCHEMA:
+        if (is_array($datum))
+        {
+          foreach ($expected_schema->fields() as $field)
+            if (!self::is_valid_datum($field->type(), $datum[$field->name()]))
+              return false;
+          return true;
+        }
+        return false;
+      default:
+        throw new AvroSchemaParseException(
+          sprintf('%s is not allowed.', $expected_schema));
+    }
+  }
+
+  /**
+   * @internal Should only be called from within the constructor of
+   *           a class which extends AvroSchema
+   * @param string $type a schema type name
+   */
+  public function __construct($type)
+  {
+    $this->type = $type;
+  }
+
+  /**
+   * @param mixed $avro
+   * @param string $default_namespace namespace of enclosing schema
+   * @param AvroNamedSchemata &$schemata
+   * @returns AvroSchema
+   * @uses AvroSchema::real_parse()
+   * @throws AvroSchemaParseException
+   */
+  protected static function subparse($avro, $default_namespace, &$schemata=null)
+  {
+    try
+    {
+      return self::real_parse($avro, $default_namespace, $schemata);
+    }
+    catch (AvroSchemaParseException $e)
+    {
+      throw $e;
+    }
+    catch (Exception $e)
+    {
+      throw new AvroSchemaParseException(
+        sprintf('Sub-schema is not a valid Avro schema. Bad schema: %s',
+                print_r($avro, true)));
+    }
+
+  }
+
+  /**
+   * @returns string schema type name of this schema
+   */
+  public function type() { return $this->type;  }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    return array(self::TYPE_ATTR => $this->type);
+  }
+
+  /**
+   * @returns string the JSON-encoded representation of this Avro schema.
+   */
+  public function __toString() { return json_encode($this->to_avro()); }
+
+  /**
+   * @returns mixed value of the attribute with the given attribute name
+   */
+  public function attribute($attribute) { return $this->$attribute(); }
+
+}
+
+/**
+ * Avro schema for basic types such as null, int, long, string.
+ * @package Avro
+ */
+class AvroPrimitiveSchema extends AvroSchema
+{
+
+  /**
+   * @param string $type the primitive schema type name
+   * @throws AvroSchemaParseException if the given $type is not a
+   *         primitive schema type name
+   */
+  public function __construct($type)
+  {
+    if (self::is_primitive_type($type))
+      return parent::__construct($type);
+    throw new AvroSchemaParseException(
+      sprintf('%s is not a valid primitive type.', $type));
+  }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = parent::to_avro();
+    // FIXME: Is this if really necessary? When *wouldn't* this be the case?
+    if (1 == count($avro))
+      return $this->type;
+    return $avro;
+  }
+}
+
+/**
+ * Avro array schema, consisting of items of a particular
+ * Avro schema type.
+ * @package Avro
+ */
+class AvroArraySchema extends AvroSchema
+{
+  /**
+   * @var AvroName|AvroSchema named schema name or AvroSchema of
+   *                          array element
+   */
+  private $items;
+
+  /**
+   * @var boolean true if the items schema
+   * FIXME: couldn't we derive this from whether or not $this->items
+   *        is an AvroName or an AvroSchema?
+   */
+  private $is_items_schema_from_schemata;
+
+  /**
+   * @param string|mixed $items AvroNamedSchema name or object form
+   *        of decoded JSON schema representation.
+   * @param string $default_namespace namespace of enclosing schema
+   * @param AvroNamedSchemata &$schemata
+   */
+  public function __construct($items, $default_namespace, &$schemata=null)
+  {
+    parent::__construct(AvroSchema::ARRAY_SCHEMA);
+
+    $this->is_items_schema_from_schemata = false;
+    $items_schema = null;
+    if (is_string($items)
+        && $items_schema = $schemata->schema_by_name(
+          new AvroName($items, null, $default_namespace)))
+      $this->is_items_schema_from_schemata = true;
+    else
+      $items_schema = AvroSchema::subparse($items, $default_namespace, $schemata);
+
+    $this->items = $items_schema;
+  }
+
+
+  /**
+   * @returns AvroName|AvroSchema named schema name or AvroSchema
+   *          of this array schema's elements.
+   */
+  public function items() { return $this->items; }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = parent::to_avro();
+    $avro[AvroSchema::ITEMS_ATTR] = $this->is_items_schema_from_schemata
+      ? $this->items->qualified_name() : $this->items->to_avro();
+    return $avro;
+  }
+}
+
+/**
+ * Avro map schema consisting of named values of defined
+ * Avro Schema types.
+ * @package Avro
+ */
+class AvroMapSchema extends AvroSchema
+{
+  /**
+   * @var string|AvroSchema named schema name or AvroSchema
+   *      of map schema values.
+   */
+  private $values;
+
+  /**
+   * @var boolean true if the named schema
+   * XXX Couldn't we derive this based on whether or not
+   * $this->values is a string?
+   */
+  private $is_values_schema_from_schemata;
+
+  /**
+   * @param string|AvroSchema $values
+   * @param string $default_namespace namespace of enclosing schema
+   * @param AvroNamedSchemata &$schemata
+   */
+  public function __construct($values, $default_namespace, &$schemata=null)
+  {
+    parent::__construct(AvroSchema::MAP_SCHEMA);
+
+    $this->is_values_schema_from_schemata = false;
+    $values_schema = null;
+    if (is_string($values)
+        && $values_schema = $schemata->schema_by_name(
+          new AvroName($values, null, $default_namespace)))
+      $this->is_values_schema_from_schemata = true;
+    else
+      $values_schema = AvroSchema::subparse($values, $default_namespace,
+                                            $schemata);
+
+    $this->values = $values_schema;
+  }
+
+  /**
+   * @returns XXX|AvroSchema
+   */
+  public function values() { return $this->values; }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = parent::to_avro();
+    $avro[AvroSchema::VALUES_ATTR] = $this->is_values_schema_from_schemata
+      ? $this->values->qualified_name() : $this->values->to_avro();
+    return $avro;
+  }
+}
+
+/**
+ * Union of Avro schemas, of which values can be of any of the schema in
+ * the union.
+ * @package Avro
+ */
+class AvroUnionSchema extends AvroSchema
+{
+  /**
+   * @var AvroSchema[] list of schemas of this union
+   */
+  private $schemas;
+
+  /**
+   * @var int[] list of indices of named schemas which
+   *                are defined in $schemata
+   */
+  private $schema_from_schemata_indices;
+
+  /**
+   * @param AvroSchema[] $schemas list of schemas in the union
+   * @param string $default_namespace namespace of enclosing schema
+   * @param AvroNamedSchemata &$schemata
+   */
+  public function __construct($schemas, $default_namespace, &$schemata=null)
+  {
+    parent::__construct(AvroSchema::UNION_SCHEMA);
+
+    $this->schema_from_schemata_indices = array();
+    $schema_types = array();
+    foreach ($schemas as $index => $schema)
+    {
+      $is_schema_from_schemata = false;
+      $new_schema = null;
+      if (is_string($schema)
+          && ($new_schema = $schemata->schema_by_name(
+                new AvroName($schema, null, $default_namespace))))
+        $is_schema_from_schemata = true;
+      else
+        $new_schema = self::subparse($schema, $default_namespace, $schemata);
+
+      $schema_type = $new_schema->type;
+      if (self::is_valid_type($schema_type)
+          && !self::is_named_type($schema_type)
+          && in_array($schema_type, $schema_types))
+        throw new AvroSchemaParseException(
+          sprintf('"%s" is already in union', $schema_type));
+      elseif (AvroSchema::UNION_SCHEMA == $schema_type)
+        throw new AvroSchemaParseException('Unions cannot contain other unions');
+      else
+      {
+        $schema_types []= $schema_type;
+        $this->schemas []= $new_schema;
+        if ($is_schema_from_schemata)
+          $this->schema_from_schemata_indices []= $index;
+      }
+    }
+
+  }
+
+  /**
+   * @returns AvroSchema[]
+   */
+  public function schemas() { return $this->schemas; }
+
+  /**
+   * @returns AvroSchema the particular schema from the union for
+   * the given (zero-based) index.
+   * @throws AvroSchemaParseException if the index is invalid for this schema.
+   */
+  public function schema_by_index($index)
+  {
+    if (count($this->schemas) > $index)
+      return $this->schemas[$index];
+
+    throw new AvroSchemaParseException('Invalid union schema index');
+  }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = array();
+
+    foreach ($this->schemas as $index => $schema)
+      $avro []= (in_array($index, $this->schema_from_schemata_indices))
+      ? $schema->qualified_name() : $schema->to_avro();
+
+    return $avro;
+  }
+}
+
+/**
+ * Parent class of named Avro schema
+ * @package Avro
+ * @todo Refactor AvroNamedSchema to use an AvroName instance
+ *       to store name information.
+ */
+class AvroNamedSchema extends AvroSchema
+{
+  /**
+   * @var AvroName $name
+   */
+  private $name;
+
+  /**
+   * @var string documentation string
+   */
+  private $doc;
+
+  /**
+   * @param string $type
+   * @param AvroName $name
+   * @param string $doc documentation string
+   * @param AvroNamedSchemata &$schemata
+   * @throws AvroSchemaParseException
+   */
+  public function __construct($type, $name, $doc=null, &$schemata=null)
+  {
+    parent::__construct($type);
+    $this->name = $name;
+
+    if ($doc && !is_string($doc))
+      throw new AvroSchemaParseException('Schema doc attribute must be a string');
+    $this->doc = $doc;
+
+    $schemata = $schemata->clone_with_new_schema($this);
+  }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = parent::to_avro();
+    list($name, $namespace) = AvroName::extract_namespace($this->qualified_name());
+    $avro[AvroSchema::NAME_ATTR] = $name;
+    if ($namespace)
+      $avro[AvroSchema::NAMESPACE_ATTR] = $namespace;
+    if (!is_null($this->doc))
+      $avro[AvroSchema::DOC_ATTR] = $this->doc;
+    return $avro;
+  }
+
+  /**
+   * @returns string
+   */
+  public function fullname() { return $this->name->fullname(); }
+
+  public function qualified_name() { return $this->name->qualified_name(); }
+
+}
+
+/**
+ * @package Avro
+ */
+class AvroName
+{
+  /**
+   * @var string character used to separate names comprising the fullname
+   */
+  const NAME_SEPARATOR = '.';
+
+  /**
+   * @var string regular expression to validate name values
+   */
+  const NAME_REGEXP = '/^[A-Za-z_][A-Za-z0-9_]*$/';
+
+  /**
+   * @returns string[] array($name, $namespace)
+   */
+  public static function extract_namespace($name, $namespace=null)
+  {
+    $parts = explode(self::NAME_SEPARATOR, $name);
+    if (count($parts) > 1)
+    {
+      $name = array_pop($parts);
+      $namespace = join(self::NAME_SEPARATOR, $parts);
+    }
+    return array($name, $namespace);
+  }
+
+  /**
+   * @returns boolean true if the given name is well-formed
+   *          (is a non-null, non-empty string) and false otherwise
+   */
+  public static function is_well_formed_name($name)
+  {
+    return (is_string($name) && !empty($name)
+            && preg_match(self::NAME_REGEXP, $name));
+  }
+
+  /**
+   * @param string $namespace
+   * @returns boolean true if namespace is composed of valid names
+   * @throws AvroSchemaParseException if any of the namespace components
+   *                                  are invalid.
+   */
+  private static function check_namespace_names($namespace)
+  {
+    foreach (explode(self::NAME_SEPARATOR, $namespace) as $n)
+    {
+      if (empty($n) || (0 == preg_match(self::NAME_REGEXP, $n)))
+        throw new AvroSchemaParseException(sprintf('Invalid name "%s"', $n));
+    }
+    return true;
+  }
+
+  /**
+   * @param string $name
+   * @param string $namespace
+   * @returns string
+   * @throws AvroSchemaParseException if any of the names are not valid.
+   */
+  private static function parse_fullname($name, $namespace)
+  {
+    if (!is_string($namespace) || empty($namespace))
+      throw new AvroSchemaParseException('Namespace must be a non-empty string.');
+    self::check_namespace_names($namespace);
+    return $namespace . '.' . $name;
+  }
+
+  /**
+   * @var string valid names are matched by self::NAME_REGEXP
+   */
+  private $name;
+
+  /**
+   * @var string
+   */
+  private $namespace;
+
+  /**
+   * @var string
+   */
+  private $fullname;
+
+  /**
+   * @var string Name qualified as necessary given its default namespace.
+   */
+  private $qualified_name;
+
+  /**
+   * @param string $name
+   * @param string $namespace
+   * @param string $default_namespace
+   */
+  public function __construct($name, $namespace, $default_namespace)
+  {
+    if (!is_string($name) || empty($name))
+      throw new AvroSchemaParseException('Name must be a non-empty string.');
+
+    if (strpos($name, self::NAME_SEPARATOR)
+        && self::check_namespace_names($name))
+      $this->fullname = $name;
+    elseif (0 == preg_match(self::NAME_REGEXP, $name))
+      throw new AvroSchemaParseException(sprintf('Invalid name "%s"', $name));
+    elseif (!is_null($namespace))
+      $this->fullname = self::parse_fullname($name, $namespace);
+    elseif (!is_null($default_namespace))
+      $this->fullname = self::parse_fullname($name, $default_namespace);
+    else
+      $this->fullname = $name;
+
+    list($this->name, $this->namespace) = self::extract_namespace($this->fullname);
+    $this->qualified_name = (is_null($this->namespace)
+                             || $this->namespace == $default_namespace)
+      ? $this->name : $this->fullname;
+  }
+
+  /**
+   * @returns array array($name, $namespace)
+   */
+  public function name_and_namespace()
+  {
+    return array($this->name, $this->namespace);
+  }
+
+  /**
+   * @returns string
+   */
+  public function fullname() { return $this->fullname; }
+
+  /**
+   * @returns string fullname
+   * @uses $this->fullname()
+   */
+  public function __toString() { return $this->fullname(); }
+
+  /**
+   * @returns string name qualified for its context
+   */
+  public function qualified_name() { return $this->qualified_name; }
+
+}
+
+/**
+ *  Keeps track of AvroNamedSchema which have been observed so far,
+ *  as well as the default namespace.
+ *
+ * @package Avro
+ */
+class AvroNamedSchemata
+{
+  /**
+   * @var AvroNamedSchema[]
+   */
+  private $schemata;
+
+  /**
+   * @param AvroNamedSchemata[]
+   */
+  public function __construct($schemata=array())
+  {
+    $this->schemata = $schemata;
+  }
+
+  /**
+   * @param string $fullname
+   * @returns boolean true if there exists a schema with the given name
+   *                  and false otherwise.
+   */
+  public function has_name($fullname)
+  {
+    return array_key_exists($fullname, $this->schemata);
+  }
+
+  /**
+   * @param string $fullname
+   * @returns AvroSchema|null the schema which has the given name,
+   *          or null if there is no schema with the given name.
+   */
+  public function schema($fullname)
+  {
+    if (isset($this->schemata[$fullname]))
+        return $this->schemata[$fullname];
+    return null;
+  }
+
+  /**
+   * @param AvroName $name
+   * @returns AvroSchema|null
+   */
+  public function schema_by_name($name)
+  {
+    return $this->schema($name->fullname());
+  }
+
+  /**
+   * Creates a new AvroNamedSchemata instance of this schemata instance
+   * with the given $schema appended.
+   * @param AvroNamedSchema schema to add to this existing schemata
+   * @returns AvroNamedSchemata
+   */
+  public function clone_with_new_schema($schema)
+  {
+    $name = $schema->fullname();
+    if (AvroSchema::is_valid_type($name))
+      throw new AvroSchemaParseException(
+        sprintf('Name "%s" is a reserved type name', $name));
+    else if ($this->has_name($name))
+      throw new AvroSchemaParseException(
+        sprintf('Name "%s" is already in use', $name));
+    $schemata = new AvroNamedSchemata($this->schemata);
+    $schemata->schemata[$name] = $schema;
+    return $schemata;
+  }
+}
+
+/**
+ * @package Avro
+ */
+class AvroEnumSchema extends AvroNamedSchema
+{
+  /**
+   * @var string[] array of symbols
+   */
+  private $symbols;
+
+  /**
+   * @param AvroName $name
+   * @param string $doc
+   * @param string[] $symbols
+   * @param AvroNamedSchemata &$schemata
+   * @throws AvroSchemaParseException
+   */
+  public function __construct($name, $doc, $symbols, &$schemata=null)
+  {
+    if (!AvroUtil::is_list($symbols))
+      throw new AvroSchemaParseException('Enum Schema symbols are not a list');
+
+    if (count(array_unique($symbols)) > count($symbols))
+      throw new AvroSchemaParseException(
+        sprintf('Duplicate symbols: %s', $symbols));
+
+    foreach ($symbols as $symbol)
+      if (!is_string($symbol) || empty($symbol))
+        throw new AvroSchemaParseException(
+          sprintf('Enum schema symbol must be a string %',
+                  print_r($symbol, true)));
+
+    parent::__construct(AvroSchema::ENUM_SCHEMA, $name, $doc, $schemata);
+    $this->symbols = $symbols;
+  }
+
+  /**
+   * @returns string[] this enum schema's symbols
+   */
+  public function symbols() { return $this->symbols; }
+
+  /**
+   * @param string $symbol
+   * @returns boolean true if the given symbol exists in this
+   *          enum schema and false otherwise
+   */
+  public function has_symbol($symbol)
+  {
+    return in_array($symbol, $this->symbols);
+  }
+
+  /**
+   * @param int $index
+   * @returns string enum schema symbol with the given (zero-based) index
+   */
+  public function symbol_by_index($index)
+  {
+    if (array_key_exists($index, $this->symbols))
+      return $this->symbols[$index];
+    throw new AvroException(sprintf('Invalid symbol index %d', $index));
+  }
+
+  /**
+   * @param string $symbol
+   * @returns int the index of the given $symbol in the enum schema
+   */
+  public function symbol_index($symbol)
+  {
+    $idx = array_search($symbol, $this->symbols, true);
+    if (false !== $idx)
+      return $idx;
+    throw new AvroException(sprintf("Invalid symbol value '%s'", $symbol));
+  }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = parent::to_avro();
+    $avro[AvroSchema::SYMBOLS_ATTR] = $this->symbols;
+    return $avro;
+  }
+}
+
+/**
+ * AvroNamedSchema with fixed-length data values
+ * @package Avro
+ */
+class AvroFixedSchema extends AvroNamedSchema
+{
+
+  /**
+   * @var int byte count of this fixed schema data value
+   */
+  private $size;
+
+  /**
+   * @param AvroName $name
+   * @param string $doc Set to null, as fixed schemas don't have doc strings
+   * @param int $size byte count of this fixed schema data value
+   * @param AvroNamedSchemata &$schemata
+   */
+  public function __construct($name, $doc, $size, &$schemata=null)
+  {
+    $doc = null; // Fixed schemas don't have doc strings.
+    if (!is_integer($size))
+      throw new AvroSchemaParseException(
+        'Fixed Schema requires a valid integer for "size" attribute');
+    parent::__construct(AvroSchema::FIXED_SCHEMA, $name, $doc, $schemata);
+    return $this->size = $size;
+  }
+
+  /**
+   * @returns int byte count of this fixed schema data value
+   */
+  public function size() { return $this->size; }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = parent::to_avro();
+    $avro[AvroSchema::SIZE_ATTR] = $this->size;
+    return $avro;
+  }
+}
+
+/**
+ * @package Avro
+ */
+class AvroRecordSchema extends AvroNamedSchema
+{
+  /**
+   * @param mixed $field_data
+   * @param string $default_namespace namespace of enclosing schema
+   * @param AvroNamedSchemata &$schemata
+   * @returns AvroField[]
+   * @throws AvroSchemaParseException
+   */
+  private static function parse_fields($field_data, $default_namespace, &$schemata)
+  {
+    $fields = array();
+    $field_names = array();
+    foreach ($field_data as $index => $field)
+    {
+      $name = AvroUtil::array_value($field, AvroField::FIELD_NAME_ATTR);
+      $type = AvroUtil::array_value($field, AvroSchema::TYPE_ATTR);
+      $order = AvroUtil::array_value($field, AvroField::ORDER_ATTR);
+
+      $default = null;
+      $has_default = false;
+      if (array_key_exists(AvroField::DEFAULT_ATTR, $field))
+      {
+        $default = $field[AvroField::DEFAULT_ATTR];
+        $has_default = true;
+      }
+
+      if (in_array($name, $field_names))
+        throw new AvroSchemaParseException(
+          sprintf("Field name %s is already in use", $name));
+
+      $is_schema_from_schemata = false;
+      $field_schema = null;
+      if (is_string($type)
+          && $field_schema = $schemata->schema_by_name(
+            new AvroName($type, null, $default_namespace)))
+        $is_schema_from_schemata = true;
+      else
+        $field_schema = self::subparse($type, $default_namespace, $schemata);
+
+      $new_field = new AvroField($name, $field_schema, $is_schema_from_schemata,
+                                 $has_default, $default, $order);
+      $field_names []= $name;
+      $fields []= $new_field;
+    }
+    return $fields;
+  }
+
+  /**
+   * @var AvroSchema[] array of AvroNamedSchema field definitions of
+   *                   this AvroRecordSchema
+   */
+  private $fields;
+
+  /**
+   * @var array map of field names to field objects.
+   * @internal Not called directly. Memoization of AvroRecordSchema->fields_hash()
+   */
+  private $fields_hash;
+
+  /**
+   * @param string $name
+   * @param string $namespace
+   * @param string $doc
+   * @param array $fields
+   * @param AvroNamedSchemata &$schemata
+   * @param string $schema_type schema type name
+   * @throws AvroSchemaParseException
+   */
+  public function __construct($name, $doc, $fields, &$schemata=null,
+                              $schema_type=AvroSchema::RECORD_SCHEMA)
+  {
+    if (is_null($fields))
+      throw new AvroSchemaParseException(
+        'Record schema requires a non-empty fields attribute');
+
+    if (AvroSchema::REQUEST_SCHEMA == $schema_type)
+      $this->type = $schema_type;
+    else
+      parent::__construct($schema_type, $name, $doc, $schemata);
+
+    list($x, $namespace) = $name->name_and_namespace();
+    $this->fields = self::parse_fields($fields, $namespace, $schemata);
+  }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = parent::to_avro();
+
+    $fields_avro = array();
+    foreach ($this->fields as $field)
+      $fields_avro [] = $field->to_avro();
+
+    if (AvroSchema::REQUEST_SCHEMA == $this->type)
+      return $fields_avro;
+
+    $avro[AvroSchema::FIELDS_ATTR] = $fields_avro;
+
+    return $avro;
+  }
+
+  /**
+   * @returns array the schema definitions of the fields of this AvroRecordSchema
+   */
+  public function fields() { return $this->fields; }
+
+  /**
+   * @returns array a hash table of the fields of this AvroRecordSchema fields
+   *          keyed by each field's name
+   */
+  public function fields_hash()
+  {
+    if (is_null($this->fields_hash))
+    {
+      $hash = array();
+      foreach ($this->fields as $field)
+        $hash[$field->name()] = $field;
+      $this->fields_hash = $hash;
+    }
+    return $this->fields_hash;
+  }
+}
+
+/**
+ * Field of an {@link AvroRecordSchema}
+ * @package Avro
+ */
+class AvroField extends AvroSchema
+{
+
+  /**
+   * @var string fields name attribute name
+   */
+  const FIELD_NAME_ATTR = 'name';
+
+  /**
+   * @var string
+   */
+  const DEFAULT_ATTR = 'default';
+
+  /**
+   * @var string
+   */
+  const ORDER_ATTR = 'order';
+
+  /**
+   * @var string
+   */
+  const ASC_SORT_ORDER = 'ascending';
+
+  /**
+   * @var string
+   */
+  const DESC_SORT_ORDER = 'descending';
+
+  /**
+   * @var string
+   */
+  const IGNORE_SORT_ORDER = 'ignore';
+
+  /**
+   * @var array list of valid field sort order values
+   */
+  private static $valid_field_sort_orders = array(self::ASC_SORT_ORDER,
+                                                  self::DESC_SORT_ORDER,
+                                                  self::IGNORE_SORT_ORDER);
+
+
+  /**
+   * @param string $order
+   * @returns boolean
+   */
+  private static function is_valid_field_sort_order($order)
+  {
+    return in_array($order, self::$valid_field_sort_orders);
+  }
+
+  /**
+   * @param string $order
+   * @throws AvroSchemaParseException if $order is not a valid
+   *                                  field order value.
+   */
+  private static function check_order_value($order)
+  {
+    if (!is_null($order) && !self::is_valid_field_sort_order($order))
+      throw new AvroSchemaParseException(
+        sprintf('Invalid field sort order %s', $order));
+  }
+
+  /**
+   * @var string
+   */
+  private $name;
+
+  /**
+   * @var boolean whether or no there is a default value
+   */
+  private $has_default;
+
+  /**
+   * @var string field default value
+   */
+  private $default;
+
+  /**
+   * @var string sort order of this field
+   */
+  private $order;
+
+  /**
+   * @var boolean whether or not the AvroNamedSchema of this field is
+   *              defined in the AvroNamedSchemata instance
+   */
+  private $is_type_from_schemata;
+
+  /**
+   * @param string $type
+   * @param string $name
+   * @param AvroSchema $schema
+   * @param boolean $is_type_from_schemata
+   * @param string $default
+   * @param string $order
+   * @todo Check validity of $default value
+   * @todo Check validity of $order value
+   */
+  public function __construct($name, $schema, $is_type_from_schemata,
+                              $has_default, $default, $order=null)
+  {
+    if (!AvroName::is_well_formed_name($name))
+      throw new AvroSchemaParseException('Field requires a "name" attribute');
+
+    $this->type = $schema;
+    $this->is_type_from_schemata = $is_type_from_schemata;
+    $this->name = $name;
+    $this->has_default = $has_default;
+    if ($this->has_default)
+      $this->default = $default;
+    $this->check_order_value($order);
+    $this->order = $order;
+  }
+
+  /**
+   * @returns mixed
+   */
+  public function to_avro()
+  {
+    $avro = array(AvroField::FIELD_NAME_ATTR => $this->name);
+
+    $avro[AvroSchema::TYPE_ATTR] = ($this->is_type_from_schemata)
+      ? $this->type->qualified_name() : $this->type->to_avro();
+
+    if (isset($this->default))
+      $avro[AvroField::DEFAULT_ATTR] = $this->default;
+
+    if ($this->order)
+      $avro[AvroField::ORDER_ATTR] = $this->order;
+
+    return $avro;
+  }
+
+  /**
+   * @returns string the name of this field
+   */
+  public function name() { return $this->name; }
+
+  /**
+   * @returns mixed the default value of this field
+   */
+  public function default_value() { return $this->default;  }
+
+  /**
+   * @returns boolean true if the field has a default and false otherwise
+   */
+  public function has_default_value() { return $this->has_default; }
+}

Added: avro/trunk/lang/php/lib/avro/util.php
URL: http://svn.apache.org/viewvc/avro/trunk/lang/php/lib/avro/util.php?rev=990860&view=auto
==============================================================================
--- avro/trunk/lang/php/lib/avro/util.php (added)
+++ avro/trunk/lang/php/lib/avro/util.php Mon Aug 30 16:50:40 2010
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @package Avro
+ */
+
+/**
+ * Class for static utility methods used in Avro.
+ *
+ * @package Avro
+ */
+class AvroUtil
+{
+  /**
+   * Determines whether the given array is an associative array
+   * (what is termed a map, hash, or dictionary in other languages)
+   * or a list (an array with monotonically increasing integer indicies
+   * starting with zero).
+   *
+   * @param array $ary array to test
+   * @returns true if the array is a list and false otherwise.
+   *
+   */
+  static function is_list($ary)
+  {
+    if (is_array($ary))
+    {
+      $i = 0;
+      foreach ($ary as $k => $v)
+      {
+        if ($i !== $k)
+          return false;
+        $i++;
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * @param array $ary
+   * @param string $key
+   * @returns mixed the value of $ary[$key] if it is set,
+   *                and null otherwise.
+   */
+  static function array_value($ary, $key)
+  {
+    return isset($ary[$key]) ? $ary[$key] : null;
+  }
+}

Propchange: avro/trunk/lang/php/test/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Aug 30 16:50:40 2010
@@ -0,0 +1 @@
+tmp

Added: avro/trunk/lang/php/test/AllTests.php
URL: http://svn.apache.org/viewvc/avro/trunk/lang/php/test/AllTests.php?rev=990860&view=auto
==============================================================================
--- avro/trunk/lang/php/test/AllTests.php (added)
+++ avro/trunk/lang/php/test/AllTests.php Mon Aug 30 16:50:40 2010
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once('DataFileTest.php');
+require_once('SchemaTest.php');
+require_once('NameTest.php');
+require_once('StringIOTest.php');
+require_once('IODatumReaderTest.php');
+require_once('LongEncodingTest.php');
+require_once('FloatIntEncodingTest.php');
+require_once('DatumIOTest.php');
+// InterOpTest tests are run separately.
+
+class AllTests
+{
+  public static function suite()
+  {
+    $suite = new PHPUnit_Framework_TestSuite('AvroAllTests');
+    $suite->addTestSuite('DataFileTest');
+    $suite->addTestSuite('SchemaTest');
+    $suite->addTestSuite('NameTest');
+    $suite->addTestSuite('StringIOTest');
+    $suite->addTestSuite('IODatumReaderTest');
+    $suite->addTestSuite('LongEncodingTest');
+    $suite->addTestSuite('FloatIntEncodingTest');
+    $suite->addTestSuite('DatumIOTest');
+    return $suite;
+  }
+}

Added: avro/trunk/lang/php/test/DataFileTest.php
URL: http://svn.apache.org/viewvc/avro/trunk/lang/php/test/DataFileTest.php?rev=990860&view=auto
==============================================================================
--- avro/trunk/lang/php/test/DataFileTest.php (added)
+++ avro/trunk/lang/php/test/DataFileTest.php Mon Aug 30 16:50:40 2010
@@ -0,0 +1,270 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once('test_helper.php');
+
+class DataFileTest extends PHPUnit_Framework_TestCase
+{
+  private $data_files;
+  const REMOVE_DATA_FILES = true;
+
+  static function current_timestamp() { return strftime("%Y%m%dT%H%M%S"); }
+
+  protected function add_data_file($data_file)
+  {
+    if (is_null($this->data_files))
+      $this->data_files = array();
+    $data_file = "$data_file.".self::current_timestamp();
+    $full = join(DIRECTORY_SEPARATOR, array(TEST_TEMP_DIR, $data_file));
+    $this->data_files []= $full;
+    return $full;
+  }
+
+  protected static function remove_data_file($data_file)
+  {
+    if (file_exists($data_file))
+      unlink($data_file);
+  }
+
+  protected function remove_data_files()
+  {
+    if (self::REMOVE_DATA_FILES
+        && 0 < count($this->data_files))
+      foreach ($this->data_files as $data_file)
+        $this->remove_data_file($data_file);
+  }
+
+  protected function setUp()
+  {
+    if (!file_exists(TEST_TEMP_DIR))
+      mkdir(TEST_TEMP_DIR);
+    $this->remove_data_files();
+  }
+  protected function tearDown()
+  {
+    $this->remove_data_files();
+  }
+
+  public function test_write_read_nothing_round_trip()
+  {
+    $data_file = $this->add_data_file('data-wr-nothing-null.avr');
+    $writers_schema = '"null"';
+    $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema);
+    $dw->close();
+
+    $dr = AvroDataIO::open_file($data_file);
+    $read_data = array_shift($dr->data());
+    $dr->close();
+    $this->assertEquals(null, $read_data);
+  }
+
+  public function test_write_read_null_round_trip()
+  {
+    $data_file = $this->add_data_file('data-wr-null.avr');
+    $writers_schema = '"null"';
+    $data = null;
+    $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema);
+    $dw->append($data);
+    $dw->close();
+
+    $dr = AvroDataIO::open_file($data_file);
+    $read_data = array_shift($dr->data());
+    $dr->close();
+    $this->assertEquals($data, $read_data);
+  }
+
+  public function test_write_read_string_round_trip()
+  {
+    $data_file = $this->add_data_file('data-wr-str.avr');
+    $writers_schema = '"string"';
+    $data = 'foo';
+    $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema);
+    $dw->append($data);
+    $dw->close();
+
+    $dr = AvroDataIO::open_file($data_file);
+    $read_data = array_shift($dr->data());
+    $dr->close();
+    $this->assertEquals($data, $read_data);
+  }
+
+
+  public function test_write_read_round_trip()
+  {
+
+    $data_file = $this->add_data_file('data-wr-int.avr');
+    $writers_schema = '"int"';
+    $data = 1;
+
+    $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema);
+    $dw->append(1);
+    $dw->close();
+
+    $dr = AvroDataIO::open_file($data_file);
+    $read_data = array_shift($dr->data());
+    $dr->close();
+    $this->assertEquals($data, $read_data);
+
+  }
+
+  public function test_write_read_true_round_trip()
+  {
+    $data_file = $this->add_data_file('data-wr-true.avr');
+    $writers_schema = '"boolean"';
+    $datum = true;
+    $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema);
+    $dw->append($datum);
+    $dw->close();
+
+    $dr = AvroDataIO::open_file($data_file);
+    $read_datum = array_shift($dr->data());
+    $dr->close();
+    $this->assertEquals($datum, $read_datum);
+  }
+
+  public function test_write_read_false_round_trip()
+  {
+    $data_file = $this->add_data_file('data-wr-false.avr');
+    $writers_schema = '"boolean"';
+    $datum = false;
+    $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema);
+    $dw->append($datum);
+    $dw->close();
+
+    $dr = AvroDataIO::open_file($data_file);
+    $read_datum = array_shift($dr->data());
+    $dr->close();
+    $this->assertEquals($datum, $read_datum);
+  }
+  public function test_write_read_int_array_round_trip()
+  {
+    $data_file = $this->add_data_file('data-wr-int-ary.avr');
+    $writers_schema = '"int"';
+    $data = array(10, 20, 30, 40, 50, 60, 70);
+    $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema);
+    foreach ($data as $datum)
+      $dw->append($datum);
+    $dw->close();
+
+    $dr = AvroDataIO::open_file($data_file);
+    $read_data = $dr->data();
+    $dr->close();
+    $this->assertEquals($data, $read_data,
+                        sprintf("in: %s\nout: %s",
+                                json_encode($data), json_encode($read_data)));
+  }
+
+  public function test_differing_schemas_with_primitives()
+  {
+    $data_file = $this->add_data_file('data-prim.avr');
+
+    $writer_schema = <<<JSON
+{ "type": "record",
+  "name": "User",
+  "fields" : [
+      {"name": "username", "type": "string"},
+      {"name": "age", "type": "int"},
+      {"name": "verified", "type": "boolean", "default": "false"}
+      ]}
+JSON;
+    $data = array(array('username' => 'john', 'age' => 25, 'verified' => true),
+                  array('username' => 'ryan', 'age' => 23, 'verified' => false));
+    $dw = AvroDataIO::open_file($data_file, 'w', $writer_schema);
+    foreach ($data as $datum)
+    {
+      $dw->append($datum);
+    }
+    $dw->close();
+    $reader_schema = <<<JSON
+      { "type": "record",
+        "name": "User",
+        "fields" : [
+      {"name": "username", "type": "string"}
+      ]}
+JSON;
+    $dr = AvroDataIO::open_file($data_file, 'r', $reader_schema);
+    foreach ($dr->data() as $index => $record)
+    {
+      $this->assertEquals($data[$index]['username'], $record['username']);
+    }
+  }
+
+  public function test_differing_schemas_with_complex_objects()
+  {
+    $data_file = $this->add_data_file('data-complex.avr');
+
+    $writers_schema = <<<JSON
+{ "type": "record",
+  "name": "something",
+  "fields": [
+    {"name": "something_fixed", "type": {"name": "inner_fixed",
+                                         "type": "fixed", "size": 3}},
+    {"name": "something_enum", "type": {"name": "inner_enum",
+                                        "type": "enum",
+                                        "symbols": ["hello", "goodbye"]}},
+    {"name": "something_array", "type": {"type": "array", "items": "int"}},
+    {"name": "something_map", "type": {"type": "map", "values": "int"}},
+    {"name": "something_record", "type": {"name": "inner_record",
+                                          "type": "record",
+                                          "fields": [
+                                            {"name": "inner", "type": "int"}
+                                          ]}},
+    {"name": "username", "type": "string"}
+]}
+JSON;
+
+    $data = array(array("username" => "john",
+                        "something_fixed" => "foo",
+                        "something_enum" => "hello",
+                        "something_array" => array(1,2,3),
+                        "something_map" => array("a" => 1, "b" => 2),
+                        "something_record" => array("inner" => 2),
+                        "something_error" => array("code" => 403)),
+                  array("username" => "ryan",
+                        "something_fixed" => "bar",
+                        "something_enum" => "goodbye",
+                        "something_array" => array(1,2,3),
+                        "something_map" => array("a" => 2, "b" => 6),
+                        "something_record" => array("inner" => 1),
+                        "something_error" => array("code" => 401)));
+    $dw = AvroDataIO::open_file($data_file, 'w', $writers_schema);
+    foreach ($data as $datum)
+      $dw->append($datum);
+    $dw->close();
+
+    foreach (array('fixed', 'enum', 'record', 'error',
+                   'array' , 'map', 'union') as $s)
+    {
+      $readers_schema = json_decode($writers_schema, true);
+      $dr = AvroDataIO::open_file($data_file, 'r', json_encode($readers_schema));
+      foreach ($dr->data() as $idx => $obj)
+      {
+        foreach ($readers_schema['fields'] as $field)
+        {
+          $field_name = $field['name'];
+          $this->assertEquals($data[$idx][$field_name], $obj[$field_name]);
+        }
+      }
+      $dr->close();
+
+    }
+
+  }
+
+}

Added: avro/trunk/lang/php/test/DatumIOTest.php
URL: http://svn.apache.org/viewvc/avro/trunk/lang/php/test/DatumIOTest.php?rev=990860&view=auto
==============================================================================
--- avro/trunk/lang/php/test/DatumIOTest.php (added)
+++ avro/trunk/lang/php/test/DatumIOTest.php Mon Aug 30 16:50:40 2010
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once('test_helper.php');
+
+class DatumIOTest extends PHPUnit_Framework_TestCase
+{
+  /**
+   * @dataProvider data_provider
+   */
+  function test_datum_round_trip($schema_json, $datum, $binary)
+  {
+    $schema = AvroSchema::parse($schema_json);
+    $written = new AvroStringIO();
+    $encoder = new AvroIOBinaryEncoder($written);
+    $writer = new AvroIODatumWriter($schema);
+
+    $writer->write($datum, $encoder);
+    $output = strval($written);
+    $this->assertEquals($binary, $output,
+                        sprintf("expected: %s\n  actual: %s",
+                                AvroDebug::ascii_string($binary, 'hex'),
+                                AvroDebug::ascii_string($output, 'hex')));
+
+    $read = new AvroStringIO($binary);
+    $decoder = new AvroIOBinaryDecoder($read);
+    $reader = new AvroIODatumReader($schema);
+    $read_datum = $reader->read($decoder);
+    $this->assertEquals($datum, $read_datum);
+  }
+
+  function data_provider()
+  {
+    return array(array('"null"', null, ''),
+
+                 array('"boolean"', true, "\001"),
+                 array('"boolean"', false, "\000"),
+
+                 array('"int"', (int) -2147483648, "\xFF\xFF\xFF\xFF\x0F"),
+                 array('"int"', -1, "\001"),
+                 array('"int"', 0, "\000"),
+                 array('"int"', 1, "\002"),
+                 array('"int"', 2147483647, "\xFE\xFF\xFF\xFF\x0F"),
+
+                 // array('"long"', (int) -9223372036854775808, "\001"),
+                 array('"long"', -1, "\001"),
+                 array('"long"',  0, "\000"),
+                 array('"long"',  1, "\002"),
+                 // array('"long"', 9223372036854775807, "\002")
+
+                 array('"float"', (float) -10.0, "\000\000 \301"),
+                 array('"float"', (float)  -1.0, "\000\000\200\277"),
+                 array('"float"', (float)   0.0, "\000\000\000\000"),
+                 array('"float"', (float)   2.0, "\000\000\000@"),
+                 array('"float"', (float)   9.0, "\000\000\020A"),
+
+                 array('"double"', (double) -10.0, "\000\000\000\000\000\000$\300"),
+                 array('"double"', (double) -1.0, "\000\000\000\000\000\000\360\277"),
+                 array('"double"', (double) 0.0, "\000\000\000\000\000\000\000\000"),
+                 array('"double"', (double) 2.0, "\000\000\000\000\000\000\000@"),
+                 array('"double"', (double) 9.0, "\000\000\000\000\000\000\"@"),
+
+                 array('"string"', 'foo', "\x06foo"),
+                 array('"bytes"', "\x01\x02\x03", "\x06\x01\x02\x03"),
+
+                 array('{"type":"array","items":"int"}',
+                       array(1,2,3),
+                       "\x06\x02\x04\x06\x00"),
+                 array('{"type":"map","values":"int"}',
+                       array('foo' => 1, 'bar' => 2, 'baz' => 3),
+                       "\x06\x06foo\x02\x06bar\x04\x06baz\x06\x00"),
+                 array('["null", "int"]', 1, "\x02\x02"),
+                 array('{"name":"fix","type":"fixed","size":3}',
+                       "\xAA\xBB\xCC", "\xAA\xBB\xCC"),
+                 array('{"name":"enm","type":"enum","symbols":["A","B","C"]}',
+                       'B', "\x02"),
+                 array('{"name":"rec","type":"record","fields":[{"name":"a","type":"int"},{"name":"b","type":"boolean"}]}',
+                       array('a' => 1, 'b' => false),
+                       "\x02\x00")
+      );
+  }
+
+  function default_provider()
+  {
+    return array(array('"null"', 'null', null),
+                 array('"boolean"', 'true', true),
+                 array('"int"', '1', 1),
+                 array('"long"', '2000', 2000),
+                 array('"float"', '1.1', (float) 1.1),
+                 array('"double"', '200.2', (double) 200.2),
+                 array('"string"', '"quux"', 'quux'),
+                 array('"bytes"', '"\u00FF"', "\xC3\xBF"),
+                 array('{"type":"array","items":"int"}',
+                       '[5,4,3,2]', array(5,4,3,2)),
+                 array('{"type":"map","values":"int"}',
+                       '{"a":9}', array('a' => 9)),
+                 array('["int","string"]', '8', 8),
+                 array('{"name":"x","type":"enum","symbols":["A","V"]}',
+                       '"A"', 'A'),
+                 array('{"name":"x","type":"fixed","size":4}', '"\u00ff"', "\xC3\xBF"),
+                 array('{"name":"x","type":"record","fields":[{"name":"label","type":"int"}]}',
+                       '{"label":7}', array('label' => 7)));
+  }
+
+  /**
+   * @dataProvider default_provider
+   */
+  function test_field_default_value($field_schema_json,
+                                    $default_json, $default_value)
+  {
+    $writers_schema_json = '{"name":"foo","type":"record","fields":[]}';
+    $writers_schema = AvroSchema::parse($writers_schema_json);
+
+    $readers_schema_json = sprintf(
+      '{"name":"foo","type":"record","fields":[{"name":"f","type":%s,"default":%s}]}',
+      $field_schema_json, $default_json);
+    $readers_schema = AvroSchema::parse($readers_schema_json);
+
+    $reader = new AvroIODatumReader($writers_schema, $readers_schema);
+    $record = $reader->read(new AvroIOBinaryDecoder(new AvroStringIO()));
+    if (array_key_exists('f', $record))
+      $this->assertEquals($default_value, $record['f']);
+    else
+      $this->assertTrue(false, sprintf('expected field record[f]: %s',
+                                       print_r($record, true))) ;
+  }
+
+}

Added: avro/trunk/lang/php/test/FloatIntEncodingTest.php
URL: http://svn.apache.org/viewvc/avro/trunk/lang/php/test/FloatIntEncodingTest.php?rev=990860&view=auto
==============================================================================
--- avro/trunk/lang/php/test/FloatIntEncodingTest.php (added)
+++ avro/trunk/lang/php/test/FloatIntEncodingTest.php Mon Aug 30 16:50:40 2010
@@ -0,0 +1,245 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+require_once('test_helper.php');
+
+class FloatIntEncodingTest extends PHPUnit_Framework_TestCase
+{
+  const FLOAT_TYPE = 'float';
+  const DOUBLE_TYPE = 'double';
+
+  static $FLOAT_NAN;
+  static $FLOAT_POS_INF;
+  static $FLOAT_NEG_INF;
+  static $DOUBLE_NAN;
+  static $DOUBLE_POS_INF;
+  static $DOUBLE_NEG_INF;
+
+  static $LONG_BITS_NAN;
+  static $LONG_BITS_POS_INF;
+  static $LONG_BITS_NEG_INF;
+  static $INT_BITS_NAN;
+  static $INT_BITS_POS_INF;
+  static $INT_BITS_NEG_INF;
+
+  static function make_special_vals()
+  {
+    self::$DOUBLE_NAN     = (double) NAN;
+    self::$DOUBLE_POS_INF = (double) INF;
+    self::$DOUBLE_NEG_INF = (double) -INF;
+    self::$FLOAT_NAN      = (float) NAN;
+    self::$FLOAT_POS_INF  = (float) INF;
+    self::$FLOAT_NEG_INF  = (float) -INF;
+
+    self::$LONG_BITS_NAN     = strrev(pack('H*', '7ff8000000000000'));
+    self::$LONG_BITS_POS_INF = strrev(pack('H*', '7ff0000000000000'));
+    self::$LONG_BITS_NEG_INF = strrev(pack('H*', 'fff0000000000000'));
+    self::$INT_BITS_NAN      = strrev(pack('H*', '7fc00000'));
+    self::$INT_BITS_POS_INF  = strrev(pack('H*', '7f800000'));
+    self::$INT_BITS_NEG_INF  = strrev(pack('H*', 'ff800000'));
+  }
+
+  function setUp()
+  {
+    self::make_special_vals();
+  }
+
+  function test_special_values()
+  {
+    $this->assertTrue(is_float(self::$FLOAT_NAN), 'float NaN is a float');
+    $this->assertTrue(is_nan(self::$FLOAT_NAN), 'float NaN is NaN');
+    $this->assertFalse(is_infinite(self::$FLOAT_NAN), 'float NaN is not infinite');
+
+    $this->assertTrue(is_float(self::$FLOAT_POS_INF), 'float pos infinity is a float');
+    $this->assertTrue(is_infinite(self::$FLOAT_POS_INF), 'float pos infinity is infinite');
+    $this->assertTrue(0 < self::$FLOAT_POS_INF, 'float pos infinity is greater than 0');
+    $this->assertFalse(is_nan(self::$FLOAT_POS_INF), 'float pos infinity is not NaN');
+
+    $this->assertTrue(is_float(self::$FLOAT_NEG_INF), 'float neg infinity is a float');
+    $this->assertTrue(is_infinite(self::$FLOAT_NEG_INF), 'float neg infinity is infinite');
+    $this->assertTrue(0 > self::$FLOAT_NEG_INF, 'float neg infinity is less than 0');
+    $this->assertFalse(is_nan(self::$FLOAT_NEG_INF), 'float neg infinity is not NaN');
+
+    $this->assertTrue(is_double(self::$DOUBLE_NAN), 'double NaN is a double');
+    $this->assertTrue(is_nan(self::$DOUBLE_NAN), 'double NaN is NaN');
+    $this->assertFalse(is_infinite(self::$DOUBLE_NAN), 'double NaN is not infinite');
+
+    $this->assertTrue(is_double(self::$DOUBLE_POS_INF), 'double pos infinity is a double');
+    $this->assertTrue(is_infinite(self::$DOUBLE_POS_INF), 'double pos infinity is infinite');
+    $this->assertTrue(0 < self::$DOUBLE_POS_INF, 'double pos infinity is greater than 0');
+    $this->assertFalse(is_nan(self::$DOUBLE_POS_INF), 'double pos infinity is not NaN');
+
+    $this->assertTrue(is_double(self::$DOUBLE_NEG_INF), 'double neg infinity is a double');
+    $this->assertTrue(is_infinite(self::$DOUBLE_NEG_INF), 'double neg infinity is infinite');
+    $this->assertTrue(0 > self::$DOUBLE_NEG_INF, 'double neg infinity is less than 0');
+    $this->assertFalse(is_nan(self::$DOUBLE_NEG_INF), 'double neg infinity is not NaN');
+
+  }
+
+  function special_vals_provider()
+  {
+    self::make_special_vals();
+    return array(array(self::DOUBLE_TYPE, self::$DOUBLE_NAN, self::$LONG_BITS_NAN),
+                 array(self::DOUBLE_TYPE, self::$DOUBLE_POS_INF, self::$LONG_BITS_POS_INF),
+                 array(self::DOUBLE_TYPE, self::$DOUBLE_NEG_INF, self::$LONG_BITS_NEG_INF),
+                 array(self::FLOAT_TYPE, self::$FLOAT_NAN, self::$INT_BITS_NAN),
+                 array(self::FLOAT_TYPE, self::$FLOAT_POS_INF, self::$INT_BITS_POS_INF),
+                 array(self::FLOAT_TYPE, self::$FLOAT_NEG_INF, self::$INT_BITS_NEG_INF));
+  }
+
+  /**
+   * @dataProvider special_vals_provider
+   */
+  function test_encoding_special_values($type, $val, $bits)
+  {
+    $this->assert_encode_values($type, $val, $bits);
+  }
+
+  function normal_vals_provider()
+  {
+    $ruby_to_generate_vals =<<<_RUBY
+      def d2lb(d); [d].pack('E') end
+      dary = (-10..10).to_a + [-1234.2132, -211e23]
+      dary.each {|x| b = d2lb(x); puts %/array(self::DOUBLE_TYPE, (double) #{x}, #{b.inspect}, '#{b.unpack('h*')[0]}'),/}
+      def f2ib(f); [f].pack('e') end
+      fary = (-10..10).to_a + [-1234.5, -211.3e6]
+      fary.each {|x| b = f2ib(x); puts %/array(self::FLOAT_TYPE, (float) #{x}, #{b.inspect}, '#{b.unpack('h*')[0]}'),/}
+_RUBY;
+
+    return array(
+                 array(self::DOUBLE_TYPE, (double) -10, "\000\000\000\000\000\000$\300", '000000000000420c'),
+                 array(self::DOUBLE_TYPE, (double) -9, "\000\000\000\000\000\000\"\300", '000000000000220c'),
+                 array(self::DOUBLE_TYPE, (double) -8, "\000\000\000\000\000\000 \300", '000000000000020c'),
+                 array(self::DOUBLE_TYPE, (double) -7, "\000\000\000\000\000\000\034\300", '000000000000c10c'),
+                 array(self::DOUBLE_TYPE, (double) -6, "\000\000\000\000\000\000\030\300", '000000000000810c'),
+                 array(self::DOUBLE_TYPE, (double) -5, "\000\000\000\000\000\000\024\300", '000000000000410c'),
+                 array(self::DOUBLE_TYPE, (double) -4, "\000\000\000\000\000\000\020\300", '000000000000010c'),
+            /**/ array(self::DOUBLE_TYPE, (double) -3, "\000\000\000\000\000\000\010\300", '000000000000800c'),
+                 array(self::DOUBLE_TYPE, (double) -2, "\000\000\000\000\000\000\000\300", '000000000000000c'),
+                 array(self::DOUBLE_TYPE, (double) -1, "\000\000\000\000\000\000\360\277", '0000000000000ffb'),
+                 array(self::DOUBLE_TYPE, (double) 0, "\000\000\000\000\000\000\000\000", '0000000000000000'),
+                 array(self::DOUBLE_TYPE, (double) 1, "\000\000\000\000\000\000\360?", '0000000000000ff3'),
+                 array(self::DOUBLE_TYPE, (double) 2, "\000\000\000\000\000\000\000@", '0000000000000004'),
+            /**/ array(self::DOUBLE_TYPE, (double) 3, "\000\000\000\000\000\000\010@", '0000000000008004'),
+                 array(self::DOUBLE_TYPE, (double) 4, "\000\000\000\000\000\000\020@", '0000000000000104'),
+                 array(self::DOUBLE_TYPE, (double) 5, "\000\000\000\000\000\000\024@", '0000000000004104'),
+                 array(self::DOUBLE_TYPE, (double) 6, "\000\000\000\000\000\000\030@", '0000000000008104'),
+                 array(self::DOUBLE_TYPE, (double) 7, "\000\000\000\000\000\000\034@", '000000000000c104'),
+                 array(self::DOUBLE_TYPE, (double) 8, "\000\000\000\000\000\000 @", '0000000000000204'),
+                 array(self::DOUBLE_TYPE, (double) 9, "\000\000\000\000\000\000\"@", '0000000000002204'),
+                 array(self::DOUBLE_TYPE, (double) 10, "\000\000\000\000\000\000$@", '0000000000004204'),
+            /**/ array(self::DOUBLE_TYPE, (double) -1234.2132, "\007\316\031Q\332H\223\300", '70ec9115ad84390c'),
+                 array(self::DOUBLE_TYPE, (double) -2.11e+25, "\311\260\276J\031t1\305", '9c0beba49147135c'),
+
+                 array(self::FLOAT_TYPE, (float) -10, "\000\000 \301", '0000021c'),
+                 array(self::FLOAT_TYPE, (float) -9, "\000\000\020\301", '0000011c'),
+                 array(self::FLOAT_TYPE, (float) -8, "\000\000\000\301", '0000001c'),
+                 array(self::FLOAT_TYPE, (float) -7, "\000\000\340\300", '00000e0c'),
+                 array(self::FLOAT_TYPE, (float) -6, "\000\000\300\300", '00000c0c'),
+                 array(self::FLOAT_TYPE, (float) -5, "\000\000\240\300", '00000a0c'),
+                 array(self::FLOAT_TYPE, (float) -4, "\000\000\200\300", '0000080c'),
+                 array(self::FLOAT_TYPE, (float) -3, "\000\000@\300", '0000040c'),
+                 array(self::FLOAT_TYPE, (float) -2, "\000\000\000\300", '0000000c'),
+                 array(self::FLOAT_TYPE, (float) -1, "\000\000\200\277", '000008fb'),
+                 array(self::FLOAT_TYPE, (float) 0, "\000\000\000\000", '00000000'),
+                 array(self::FLOAT_TYPE, (float) 1, "\000\000\200?", '000008f3'),
+                 array(self::FLOAT_TYPE, (float) 2, "\000\000\000@", '00000004'),
+                 array(self::FLOAT_TYPE, (float) 3, "\000\000@@", '00000404'),
+                 array(self::FLOAT_TYPE, (float) 4, "\000\000\200@", '00000804'),
+                 array(self::FLOAT_TYPE, (float) 5, "\000\000\240@", '00000a04'),
+                 array(self::FLOAT_TYPE, (float) 6, "\000\000\300@", '00000c04'),
+                 array(self::FLOAT_TYPE, (float) 7, "\000\000\340@", '00000e04'),
+                 array(self::FLOAT_TYPE, (float) 8, "\000\000\000A", '00000014'),
+                 array(self::FLOAT_TYPE, (float) 9, "\000\000\020A", '00000114'),
+                 array(self::FLOAT_TYPE, (float) 10, "\000\000 A", '00000214'),
+                 array(self::FLOAT_TYPE, (float) -1234.5, "\000P\232\304", '0005a94c'),
+                 array(self::FLOAT_TYPE, (float) -211300000.0, "\352\202I\315", 'ae2894dc'),
+      );
+  }
+
+  function float_vals_provider()
+  {
+    $ary = array();
+
+    foreach ($this->normal_vals_provider() as $values)
+      if (self::FLOAT_TYPE == $values[0])
+        $ary []= array($values[0], $values[1], $values[2]);
+
+    return $ary;
+  }
+
+  function double_vals_provider()
+  {
+    $ary = array();
+
+    foreach ($this->normal_vals_provider() as $values)
+      if (self::DOUBLE_TYPE == $values[0])
+        $ary []= array($values[0], $values[1], $values[2]);
+
+    return $ary;
+  }
+
+
+  /**
+   * @dataProvider float_vals_provider
+   */
+  function test_encoding_float_values($type, $val, $bits)
+  {
+    $this->assert_encode_values($type, $val, $bits);
+  }
+
+  /**
+   * @dataProvider double_vals_provider
+   */
+  function test_encoding_double_values($type, $val, $bits)
+  {
+    $this->assert_encode_values($type, $val, $bits);
+  }
+
+  function assert_encode_values($type, $val, $bits)
+  {
+    if (self::FLOAT_TYPE == $type)
+    {
+      $decoder = array('AvroIOBinaryDecoder', 'int_bits_to_float');
+      $encoder = array('AvroIOBinaryEncoder', 'float_to_int_bits');
+    }
+    else
+    {
+      $decoder = array('AvroIOBinaryDecoder', 'long_bits_to_double');
+      $encoder = array('AvroIOBinaryEncoder', 'double_to_long_bits');
+    }
+
+    $decoded_bits_val = call_user_func($decoder, $bits);
+    $this->assertEquals($val, $decoded_bits_val,
+                        sprintf("%s\n expected: '%f'\n    given: '%f'",
+                                'DECODED BITS', $val, $decoded_bits_val));
+
+    $encoded_val_bits = call_user_func($encoder, $val);
+    $this->assertEquals($bits, $encoded_val_bits,
+                        sprintf("%s\n expected: '%s'\n    given: '%s'",
+                                'ENCODED VAL',
+                                AvroDebug::hex_string($bits),
+                                AvroDebug::hex_string($encoded_val_bits)));
+
+    $round_trip_value = call_user_func($decoder, $encoded_val_bits);
+    $this->assertEquals($val, $round_trip_value,
+                        sprintf("%s\n expected: '%f'\n     given: '%f'",
+                                'ROUND TRIP BITS', $val, $round_trip_value));
+  }
+
+}

Added: avro/trunk/lang/php/test/IODatumReaderTest.php
URL: http://svn.apache.org/viewvc/avro/trunk/lang/php/test/IODatumReaderTest.php?rev=990860&view=auto
==============================================================================
--- avro/trunk/lang/php/test/IODatumReaderTest.php (added)
+++ avro/trunk/lang/php/test/IODatumReaderTest.php Mon Aug 30 16:50:40 2010
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once('test_helper.php');
+
+class IODatumReaderTest extends PHPUnit_Framework_TestCase
+{
+
+  public function testSchemaMatching()
+  {
+    $writers_schema = <<<JSON
+      { "type": "map",
+        "values": "bytes" }
+JSON;
+    $readers_schema = $writers_schema;
+    $this->assertTrue(AvroIODatumReader::schemas_match(
+                        AvroSchema::parse($writers_schema),
+                        AvroSchema::parse($readers_schema)));
+  }
+}

Added: avro/trunk/lang/php/test/InterOpTest.php
URL: http://svn.apache.org/viewvc/avro/trunk/lang/php/test/InterOpTest.php?rev=990860&view=auto
==============================================================================
--- avro/trunk/lang/php/test/InterOpTest.php (added)
+++ avro/trunk/lang/php/test/InterOpTest.php Mon Aug 30 16:50:40 2010
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once('test_helper.php');
+
+class InterOpTest extends PHPUnit_Framework_TestCase
+{
+  var $projection_json;
+  var $projection;
+
+  public function setUp()
+  {
+    $interop_schema_file_name = AVRO_INTEROP_SCHEMA;
+    $this->projection_json = file_get_contents($interop_schema_file_name);
+    $this->projection = AvroSchema::parse($this->projection_json);
+  }
+
+  public function file_name_provider()
+  {
+    $data_dir = AVRO_BUILD_DATA_DIR;
+    $data_files = array();
+    if (!($dh = opendir($data_dir)))
+      die("Could not open data dir '$data_dir'\n");
+    while ($file = readdir($dh))
+      if (0 < preg_match('/.*\.avro$/', $file))
+        $data_files []= join(DIRECTORY_SEPARATOR, array($data_dir, $file));
+    closedir($dh);
+
+    $ary = array();
+    foreach ($data_files as $df)
+      $ary []= array($df);
+    return $ary;
+  }
+
+  /**
+   *  @dataProvider file_name_provider
+   */
+  public function test_read($file_name)
+  {
+
+    $dr = AvroDataIO::open_file(
+      $file_name, AvroFile::READ_MODE, $this->projection_json);
+
+    $data = $dr->data();
+
+    $this->assertNotEquals(0, count($data),
+                           sprintf("no data read from %s", $file_name));
+
+    foreach ($data as $idx => $datum)
+      $this->assertNotNull($datum, sprintf("null datum from %s", $file_name));
+
+  }
+
+}