diff --git a/canonical.go b/canonical.go
index fe69f45..32ac608 100644
--- a/canonical.go
+++ b/canonical.go
@@ -84,7 +84,7 @@ func pcfObject(jsonMap map[string]interface{}, parentNamespace string, typeLooku
 			}
 			parentNamespace = namespace
 		}
-	} else if objectType, ok := jsonMap["type"]; ok && objectType == "record" {
+	} else if objectType, ok := jsonMap["type"]; ok && (objectType == "record" || objectType == "enum" || objectType == "fixed") {
 		namespace = parentNamespace
 	}
 
diff --git a/canonical_test.go b/canonical_test.go
index dd43dba..811e0eb 100644
--- a/canonical_test.go
+++ b/canonical_test.go
@@ -154,6 +154,18 @@ func TestCanonicalSchema(t *testing.T) {
 			Schema:    `{"namespace":"x.y.z", "type":"enum", "name":"foo", "doc":"foo bar", "symbols":["A1", "A2"]}`,
 			Canonical: `{"name":"x.y.z.foo","type":"enum","symbols":["A1","A2"]}`,
 		},
+		{
+			Schema:    `{"type":"record", "name":"a.b.foo", "namespace":"x.y", "fields":[{"name":"bar","type":"enum","symbols":["A1","A2"]}]}`,
+			Canonical: `{"name":"a.b.foo","type":"record","fields":[{"name":"x.y.bar","type":"enum","symbols":["A1","A2"]}]}`,
+		},
+		{
+			Schema:    `{"type":"record", "name":"foo", "namespace":"x.y", "fields":[{"name":"bar","type":"enum","symbols":["A1","A2"]}]}`,
+			Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"x.y.bar","type":"enum","symbols":["A1","A2"]}]}`,
+		},
+		{
+			Schema:    `{"type":"record", "name":"foo", "namespace":"x.y", "fields":[{"name":"a.b.bar","type":"enum","symbols":["A1","A2"]}]}`,
+			Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"a.b.bar","type":"enum","symbols":["A1","A2"]}]}`,
+		},
 		{
 			Schema:    `{"name":"foo","type":"fixed","size":15}`,
 			Canonical: `{"name":"foo","type":"fixed","size":15}`,
@@ -162,6 +174,14 @@ func TestCanonicalSchema(t *testing.T) {
 			Schema:    `{"namespace":"x.y.z", "type":"fixed", "name":"foo", "doc":"foo bar", "size":32}`,
 			Canonical: `{"name":"x.y.z.foo","type":"fixed","size":32}`,
 		},
+		{
+			Schema:    `{"type":"record", "name":"foo", "namespace":"x.y", "fields":[{"name":"bar","type":"fixed", "doc":"foo bar", "size":32}]}`,
+			Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"x.y.bar","type":"fixed","size":32}]}`,
+		},
+		{
+			Schema:    `{"type":"record", "name":"foo", "namespace":"x.y", "fields":[{"name":"a.b.bar","type":"fixed", "doc":"foo bar", "size":32}]}`,
+			Canonical: `{"name":"x.y.foo","type":"record","fields":[{"name":"a.b.bar","type":"fixed","size":32}]}`,
+		},
 		{
 			Schema:    `{ "items":{"type":"null"}, "type":"array"}`,
 			Canonical: `{"type":"array","items":"null"}`,
diff --git a/codec.go b/codec.go
index 9e3aac4..ac05b10 100644
--- a/codec.go
+++ b/codec.go
@@ -566,11 +566,26 @@ func buildCodecForTypeDescribedByString(st map[string]*Codec, enclosingNamespace
 		isLogicalType = true
 		searchType = fmt.Sprintf("%s.%s", typeName, lt)
 	}
+
 	// NOTE: When codec already exists, return it. This includes both primitive and
 	// logicalType codecs added in NewCodec, and user-defined types, added while
 	// building the codec.
 	if cd, ok := st[searchType]; ok {
-		return cd, nil
+
+		// For "bytes.decimal" types verify that the scale and precision in this schema map match a cached codec before
+		// using the cached codec in favor of creating a new codec.
+		if searchType == "bytes.decimal" {
+
+			// Search the cached codecs for a "bytes.decimal" codec  with a "precision" and "scale" specified in the key,
+			// only if that matches return the cached codec. Otherwise, create a new codec for this "bytes.decimal".
+			decimalSearchType := fmt.Sprintf("bytes.decimal.%d.%d", int(schemaMap["precision"].(float64)), int(schemaMap["scale"].(float64)))
+			if cd2, ok := st[decimalSearchType]; ok {
+				return cd2, nil
+			}
+
+		} else {
+			return cd, nil
+		}
 	}
 
 	// Avro specification allows abbreviation of type name inside a namespace.
@@ -596,6 +611,8 @@ func buildCodecForTypeDescribedByString(st map[string]*Codec, enclosingNamespace
 		return makeDecimalBytesCodec(st, enclosingNamespace, schemaMap)
 	case "fixed.decimal":
 		return makeDecimalFixedCodec(st, enclosingNamespace, schemaMap)
+	case "string.validated-string":
+		return makeValidatedStringCodec(st, enclosingNamespace, schemaMap)
 	default:
 		if isLogicalType {
 			delete(schemaMap, "logicalType")
diff --git a/codec_test.go b/codec_test.go
index 268d431..b4729e5 100644
--- a/codec_test.go
+++ b/codec_test.go
@@ -300,3 +300,34 @@ func ExampleSingleItemDecoding() {
 	fmt.Println(datum)
 	// Output: 3
 }
+
+func Test_buildCodecForTypeDescribedByString_CacheRespectsPrecisionScale(t *testing.T) {
+	schemaMap := map[string]interface{}{
+		"type":        "bytes",
+		"logicalType": "decimal",
+		"precision":   float64(4),
+		"scale":       float64(2),
+	}
+	cachedCodecIdentifier := "preexisting-cached-coded"
+	cache := map[string]*Codec{
+		"bytes.decimal": nil, // precision.scale-agnostic codec
+		"bytes.decimal.4.2": {
+			schemaOriginal: cachedCodecIdentifier, // using field as identifier
+		},
+	}
+
+	// cached bytes.decimal codec with matching precision.scale is returned
+	cacheHit, err := buildCodecForTypeDescribedByString(cache, "", "bytes", schemaMap, nil)
+	ensureError(t, err) // ensure NO error
+	if cacheHit.schemaOriginal != cachedCodecIdentifier {
+		t.Errorf("GOT: %v; WANT: %v", cacheHit.schemaOriginal, cachedCodecIdentifier)
+	}
+
+	// cached codec with unmatching precision.scale is not returned
+	schemaMap["scale"] = float64(1)
+	cacheMiss, err := buildCodecForTypeDescribedByString(cache, "", "bytes", schemaMap, nil)
+	ensureError(t, err) // ensure NO error
+	if cacheMiss.schemaOriginal == cachedCodecIdentifier {
+		t.Errorf("GOT: %v; WANT: %v", cacheMiss.schemaOriginal, "!= "+cachedCodecIdentifier)
+	}
+}
diff --git a/examples/165/main.go b/examples/165/main.go
index b1cf4ce..687740c 100644
--- a/examples/165/main.go
+++ b/examples/165/main.go
@@ -37,7 +37,7 @@ package main
 import (
 	"os"
 
-	"github.com/linkedin/goavro"
+	"github.com/linkedin/goavro/v2"
 )
 
 const loginEventAvroSchema = `{"type": "record", "name": "LoginEvent", "fields": [{"name": "Username", "type": "string"}]}`
diff --git a/examples/soe/main.go b/examples/soe/main.go
index a615024..21c76ce 100644
--- a/examples/soe/main.go
+++ b/examples/soe/main.go
@@ -3,7 +3,7 @@ package main
 import (
 	"fmt"
 
-	"github.com/linkedin/goavro"
+	"github.com/linkedin/goavro/v2"
 )
 
 func main() {
diff --git a/logical_type.go b/logical_type.go
index 94ce35a..700980f 100644
--- a/logical_type.go
+++ b/logical_type.go
@@ -13,12 +13,16 @@ import (
 	"errors"
 	"fmt"
 	"math/big"
+	"regexp"
+	"strings"
 	"time"
 )
 
 type toNativeFn func([]byte) (interface{}, []byte, error)
 type fromNativeFn func([]byte, interface{}) ([]byte, error)
 
+var reFromPattern = make(map[string]*regexp.Regexp)
+
 //////////////////////////////////////////////////////////////////////////////////////////////
 // date logical type - to/from time.Time, time.UTC location
 //////////////////////////////////////////////////////////////////////////////////////////////
@@ -123,7 +127,7 @@ func timeMicrosFromNative(fn fromNativeFn) fromNativeFn {
 			return fn(b, val)
 
 		case time.Duration:
-			duration := int32(val.Nanoseconds() / int64(time.Microsecond))
+			duration := val.Nanoseconds() / int64(time.Microsecond)
 			return fn(b, duration)
 
 		default:
@@ -233,14 +237,14 @@ func precisionAndScaleFromSchemaMap(schemaMap map[string]interface{}) (int, int,
 		return 0, 0, fmt.Errorf("cannot create decimal logical type with wrong precision type; expected: float64; received: %T", p1)
 	}
 	p3 := int(p2)
-	if p3 <= 1 {
+	if p3 < 1 {
 		return 0, 0, fmt.Errorf("cannot create decimal logical type when precision is less than one: %d", p3)
 	}
 	var s3 int // scale defaults to 0 if not set
 	if s1, ok := schemaMap["scale"]; ok {
 		s2, ok := s1.(float64)
 		if !ok {
-			return 0, 0, fmt.Errorf("cannot create decimal logical type with wrong precision type; expected: float64; received: %T", p1)
+			return 0, 0, fmt.Errorf("cannot create decimal logical type with wrong scale type; expected: float64; received: %T", s1)
 		}
 		s3 = int(s2)
 		if s3 < 0 {
@@ -267,6 +271,11 @@ func makeDecimalBytesCodec(st map[string]*Codec, enclosingNamespace string, sche
 	if err != nil {
 		return nil, fmt.Errorf("Bytes ought to have valid name: %s", err)
 	}
+
+	// Add an additional cached codec for this "bytes.decimal" keyed also by "precision" and "scale"
+	decimalSearchType := fmt.Sprintf("bytes.decimal.%d.%d", precision, scale)
+	st[decimalSearchType] = c
+
 	c.binaryFromNative = decimalBytesFromNative(bytesBinaryFromNative, toSignedBytes, precision, scale)
 	c.textualFromNative = decimalBytesFromNative(bytesTextualFromNative, toSignedBytes, precision, scale)
 	c.nativeFromBinary = nativeFromDecimalBytes(bytesNativeFromBinary, precision, scale)
@@ -335,6 +344,85 @@ func makeDecimalFixedCodec(st map[string]*Codec, enclosingNamespace string, sche
 	return c, nil
 }
 
+func makeValidatedStringCodec(st map[string]*Codec, enclosingNamespace string, schemaMap map[string]interface{}) (*Codec, error) {
+	pattern, ok := schemaMap["pattern"]
+	if !ok {
+		return nil, errors.New("cannot create validated-string logical type without pattern")
+	}
+
+	patternStr := strings.TrimSpace(pattern.(string))
+	if reFromPattern[patternStr] == nil {
+		var (
+			regexpr *regexp.Regexp
+			err     error
+		)
+		if regexpr, err = regexp.Compile(patternStr); err != nil {
+			return nil, err
+		}
+
+		reFromPattern[patternStr] = regexpr
+	}
+
+	if _, ok := schemaMap["name"]; !ok {
+		schemaMap["name"] = "string.validated-string"
+	}
+
+	c, err := registerNewCodec(st, schemaMap, enclosingNamespace)
+	if err != nil {
+		return nil, err
+	}
+
+	c.binaryFromNative = validatedStringBinaryFromNative(c.binaryFromNative)
+	c.textualFromNative = validatedStringTextualFromNative(c.textualFromNative)
+	c.nativeFromBinary = validatedStringNativeFromBinary(c.nativeFromBinary, patternStr)
+	c.nativeFromTextual = validatedStringNativeFromTextual(c.nativeFromTextual, patternStr)
+	return c, nil
+}
+
+func validatedStringBinaryFromNative(fromNativeFn fromNativeFn) fromNativeFn {
+	return func(b []byte, d interface{}) ([]byte, error) {
+		return stringBinaryFromNative(b, d)
+	}
+}
+
+func validatedStringTextualFromNative(fromNativeFn fromNativeFn) fromNativeFn {
+	return func(b []byte, d interface{}) ([]byte, error) {
+		return stringTextualFromNative(b, d)
+	}
+}
+
+func validatedStringNativeFromBinary(fn toNativeFn, pattern string) toNativeFn {
+	return func(bytes []byte) (interface{}, []byte, error) {
+		fn, newBytes, err := stringNativeFromBinary(bytes)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		result := fn.(string)
+		if ok := reFromPattern[pattern].MatchString(result); !ok {
+			return nil, bytes, fmt.Errorf("cannot match input string against validation pattern: %q does not match %q", result, pattern)
+		}
+
+		return fn, newBytes, nil
+	}
+}
+
+func validatedStringNativeFromTextual(fn toNativeFn, pattern string) toNativeFn {
+	return func(bytes []byte) (interface{}, []byte, error) {
+		fn, newBytes, err := stringNativeFromTextual(bytes)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		result := fn.(string)
+		if ok := reFromPattern[pattern].MatchString(result); !ok {
+			return nil, bytes, fmt.Errorf("cannot match input string against validation pattern: %q does not match %q", result, pattern)
+		}
+
+		return fn, newBytes, nil
+	}
+}
+
 func padBytes(bytes []byte, fixedSize uint) []byte {
 	s := int(fixedSize)
 	padded := make([]byte, s, s)
diff --git a/logical_type_test.go b/logical_type_test.go
index d3e2bec..20f7903 100644
--- a/logical_type_test.go
+++ b/logical_type_test.go
@@ -11,11 +11,17 @@ package goavro
 
 import (
 	"fmt"
+	"math"
 	"math/big"
 	"testing"
 	"time"
 )
 
+const (
+	precision = "precision"
+	scale     = "scale"
+)
+
 func TestSchemaLogicalType(t *testing.T) {
 	testSchemaValid(t, `{"type": "long", "logicalType": "timestamp-millis"}`)
 	testSchemaInvalid(t, `{"type": "bytes", "logicalType": "decimal"}`, "precision")
@@ -37,8 +43,7 @@ func TestLongLogicalTypeFallback(t *testing.T) {
 func TestTimeStampMillisLogicalTypeEncode(t *testing.T) {
 	schema := `{"type": "long", "logicalType": "timestamp-millis"}`
 	testBinaryDecodeFail(t, schema, []byte(""), "short buffer")
-	t.Skip("this test is broken")
-	testBinaryEncodeFail(t, schema, "test", "cannot transform binary timestamp-millis, expected time.Time")
+	testBinaryEncodeFail(t, schema, "test", "cannot transform to binary timestamp-millis, expected time.Time or Go numeric")
 	testBinaryCodecPass(t, schema, time.Date(2006, 1, 2, 15, 04, 05, 565000000, time.UTC), []byte("\xfa\x82\xac\xba\x91\x42"))
 }
 
@@ -51,8 +56,7 @@ func TestTimeStampMillisLogicalTypeUnionEncode(t *testing.T) {
 func TestTimeStampMicrosLogicalTypeEncode(t *testing.T) {
 	schema := `{"type": "long", "logicalType": "timestamp-micros"}`
 	testBinaryDecodeFail(t, schema, []byte(""), "short buffer")
-	t.Skip("this test is broken")
-	testBinaryEncodeFail(t, schema, "test", "cannot transform binary timestamp-micros, expected time.Time")
+	testBinaryEncodeFail(t, schema, "test", "cannot transform to binary timestamp-micros, expected time.Time or Go numeric")
 	testBinaryCodecPass(t, schema, time.Date(2006, 1, 2, 15, 04, 05, 565283000, time.UTC), []byte("\xc6\x8d\xf7\xe7\xaf\xd8\x84\x04"))
 }
 
@@ -79,21 +83,18 @@ func TestTimeMicrosLogicalTypeEncode(t *testing.T) {
 	schema := `{"type": "long", "logicalType": "time-micros"}`
 	testBinaryDecodeFail(t, schema, []byte(""), "short buffer")
 	testBinaryEncodeFail(t, schema, "test", "cannot transform to binary time-micros, expected time.Duration")
-	t.Skip("this test is broken")
 	testBinaryCodecPass(t, schema, 66904022566*time.Microsecond, []byte("\xcc\xf8\xd2\xbc\xf2\x03"))
 }
 
 func TestTimeMicrosLogicalTypeUnionEncode(t *testing.T) {
 	schema := `{"type": ["null", {"type": "long", "logicalType": "time-micros"}]}`
 	testBinaryEncodeFail(t, schema, Union("string", "test"), "cannot encode binary union: no member schema types support datum: allowed types: [null long.time-micros]")
-	t.Skip("this test is broken")
 	testBinaryCodecPass(t, schema, Union("long.time-micros", 66904022566*time.Microsecond), []byte("\x02\xcc\xf8\xd2\xbc\xf2\x03"))
 }
 func TestDateLogicalTypeEncode(t *testing.T) {
 	schema := `{"type": "int", "logicalType": "date"}`
 	testBinaryDecodeFail(t, schema, []byte(""), "short buffer")
-	t.Skip("this test is broken")
-	testBinaryEncodeFail(t, schema, "test", "cannot transform to binary date, expected time.Time, received string")
+	testBinaryEncodeFail(t, schema, "test", "cannot transform to binary date, expected time.Time or Go numeric, received string")
 	testBinaryCodecPass(t, schema, time.Date(2006, 1, 2, 0, 0, 0, 0, time.UTC), []byte("\xbc\xcd\x01"))
 }
 
@@ -163,6 +164,12 @@ func TestDecimalFixedLogicalTypeEncode(t *testing.T) {
 	// Encodes to 12 due to scale: 0
 	testBinaryEncodePass(t, schema0scale, big.NewRat(617, 50), []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c"))
 	testBinaryDecodePass(t, schema0scale, big.NewRat(12, 1), []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c"))
+
+	schemaPrecision1 := `{"type": "fixed", "size": 4, "logicalType": "decimal", "precision": 1, "scale": 1}`
+	testBinaryCodecPass(t, schemaPrecision1, big.NewRat(163, 10), []byte("\x00\x00\x00\xa3"))
+	testBinaryCodecPass(t, schemaPrecision1, big.NewRat(-130, 4), []byte("\xff\xff\xfe\xbb"))
+	testBinaryCodecPass(t, schemaPrecision1, big.NewRat(25, 2), []byte("\x00\x00\x00\x7d"))
+	testBinaryEncodeFail(t, schemaPrecision1, big.NewRat(math.MaxInt32, -1), "datum size ought to equal schema size")
 }
 
 func TestDecimalBytesLogicalTypeInRecordEncode(t *testing.T) {
@@ -171,6 +178,65 @@ func TestDecimalBytesLogicalTypeInRecordEncode(t *testing.T) {
 	testBinaryCodecPass(t, schema, map[string]interface{}{"mydecimal": big.NewRat(617, 50)}, []byte("\x04\x04\xd2"))
 }
 
+func TestValidatedStringLogicalTypeInRecordEncode(t *testing.T) {
+	schema := `{
+		"type": "record",
+		"name": "myrecord",
+		"fields": [
+			{
+				"name": "number",
+				"doc": "Phone number inside the national network. Length between 4-14",
+				"type": {
+					  "type": "string",
+					  "logicalType": "validatedString",
+					  "pattern": "^[\\d]{4,14}$"
+				}
+			}
+		]
+	  }`
+
+	codec, err := NewCodec(schema)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// NOTE: May omit fields when using default value
+	textual := []byte(`{"number": "667777777"}`)
+
+	// Convert textual Avro data (in Avro JSON format) to native Go form
+	native, _, err := codec.NativeFromTextual(textual)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Convert native Go form to binary Avro data
+	binary, err := codec.BinaryFromNative(nil, native)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	testSchemaValid(t, schema)
+	testBinaryCodecPass(t, schema, map[string]interface{}{"number": "667777777"}, binary)
+
+	// Convert binary Avro data back to native Go form
+	native, _, err = codec.NativeFromBinary(binary)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Convert native Go form to textual Avro data
+	textual, err = codec.TextualFromNative(nil, native)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// NOTE: Textual encoding will show all fields, even those with values that
+	// match their default values
+	if got, want := string(textual), "{\"number\":\"667777777\"}"; got != want {
+		t.Errorf("GOT: %v; WANT: %v", got, want)
+	}
+}
+
 func ExampleUnion_logicalType() {
 	// Supported logical types and their native go types:
 	// * timestamp-millis - time.Time
@@ -199,3 +265,43 @@ func ExampleUnion_logicalType() {
 	fmt.Printf("%#v\n", out["long.timestamp-millis"].(time.Time).String())
 	// Output: "2006-01-02 15:04:05 +0000 UTC"
 }
+
+func TestPrecisionAndScaleFromSchemaMapValidation(t *testing.T) {
+	testCasesInvalid := []struct {
+		schemaMap map[string]interface{}
+		errMsg    string
+	}{
+		{map[string]interface{}{}, "cannot create decimal logical type without precision"},
+		{map[string]interface{}{
+			precision: true,
+		}, "wrong precision type"},
+		{map[string]interface{}{
+			precision: float64(0),
+		}, "precision is less than one"},
+		{map[string]interface{}{
+			precision: float64(2),
+			scale:     true,
+		}, "wrong scale type"},
+		{map[string]interface{}{
+			precision: float64(2),
+			scale:     float64(-1),
+		}, "scale is less than zero"},
+		{map[string]interface{}{
+			precision: float64(2),
+			scale:     float64(3),
+		}, "scale is larger than precision"},
+	}
+	for _, tc := range testCasesInvalid {
+		_, _, err := precisionAndScaleFromSchemaMap(tc.schemaMap)
+		ensureError(t, err, tc.errMsg)
+	}
+
+	// validation passes
+	p, s, err := precisionAndScaleFromSchemaMap(map[string]interface{}{
+		precision: float64(1),
+		scale:     float64(1),
+	})
+	if p != 1 || s != 1 || err != nil {
+		t.Errorf("GOT: %v %v %v; WANT: 1 1 nil", p, s, err)
+	}
+}
diff --git a/ocf_writer.go b/ocf_writer.go
index 820af5c..32ec042 100644
--- a/ocf_writer.go
+++ b/ocf_writer.go
@@ -52,9 +52,9 @@ type OCFConfig struct {
 	// this field is ignored.
 	CompressionName string
 
-	//MetaData specifies application specific meta data to be added to
-	//the OCF file.  When appending to an existing OCF, this field
-	//is ignored
+	// MetaData specifies application specific meta data to be added to
+	// the OCF file.  When appending to an existing OCF, this field
+	// is ignored.
 	MetaData map[string][]byte
 }