You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by fo...@apache.org on 2024/05/28 14:57:57 UTC

(iceberg-go) branch main updated: feat: Adding literals (#76)

This is an automated email from the ASF dual-hosted git repository.

fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 3c37b10  feat: Adding literals (#76)
3c37b10 is described below

commit 3c37b100213c4a78d17ce443ce0978a78b724759
Author: Matt Topol <zo...@gmail.com>
AuthorDate: Tue May 28 10:57:53 2024 -0400

    feat: Adding literals (#76)
    
    * feat: Adding literals
    
    * implementing feedback
---
 errors.go        |   3 +
 go.mod           |  47 +++-
 go.sum           | 105 +++++---
 literals.go      | 780 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 literals_test.go | 729 +++++++++++++++++++++++++++++++++++++++++++++++++++
 types.go         |  14 +
 utils.go         |   5 +-
 7 files changed, 1635 insertions(+), 48 deletions(-)

diff --git a/errors.go b/errors.go
index c5bd870..71f9f63 100644
--- a/errors.go
+++ b/errors.go
@@ -25,4 +25,7 @@ var (
 	ErrInvalidArgument   = errors.New("invalid argument")
 	ErrInvalidSchema     = errors.New("invalid schema")
 	ErrInvalidTransform  = errors.New("invalid transform syntax")
+	ErrType              = errors.New("type error")
+	ErrBadCast           = errors.New("could not cast value")
+	ErrBadLiteral        = errors.New("invalid literal value")
 )
diff --git a/go.mod b/go.mod
index 046ee7b..5accc3f 100644
--- a/go.mod
+++ b/go.mod
@@ -20,31 +20,35 @@ module github.com/apache/iceberg-go
 go 1.21
 
 require (
+	github.com/apache/arrow/go/v16 v16.0.0
 	github.com/aws/aws-sdk-go-v2 v1.27.0
-	github.com/aws/aws-sdk-go-v2/config v1.27.15
-	github.com/aws/aws-sdk-go-v2/credentials v1.17.15
-	github.com/aws/aws-sdk-go-v2/service/glue v1.81.1
-	github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3
+	github.com/aws/aws-sdk-go-v2/config v1.26.6
+	github.com/aws/aws-sdk-go-v2/credentials v1.17.12
+	github.com/aws/aws-sdk-go-v2/service/glue v1.73.1
+	github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0
 	github.com/aws/smithy-go v1.20.2
 	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
 	github.com/google/uuid v1.6.0
-	github.com/hamba/avro/v2 v2.22.0
-	github.com/pterm/pterm v0.12.79
+	github.com/hamba/avro/v2 v2.21.1
+	github.com/pterm/pterm v0.12.78
 	github.com/stretchr/testify v1.9.0
 	github.com/wolfeidau/s3iofs v1.5.2
-	golang.org/x/exp v0.0.0-20231006140011-7918f672742d
+	golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
 )
 
 require (
 	atomicgo.dev/cursor v0.2.0 // indirect
 	atomicgo.dev/keyboard v0.2.9 // indirect
 	atomicgo.dev/schedule v0.1.0 // indirect
-	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
-	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect
+	github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect
+	github.com/andybalholm/brotli v1.1.0 // indirect
+	github.com/apache/thrift v0.20.0 // indirect
+	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect
@@ -54,22 +58,37 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 // indirect
 	github.com/containerd/console v1.0.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
+	github.com/google/flatbuffers v24.3.25+incompatible // indirect
 	github.com/gookit/color v1.5.4 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/asmfmt v1.3.2 // indirect
 	github.com/klauspost/compress v1.17.8 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
 	github.com/lithammer/fuzzysearch v1.1.8 // indirect
 	github.com/mattn/go-runewidth v0.0.15 // indirect
+	github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
+	github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pierrec/lz4/v4 v4.1.21 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rivo/uniseg v0.4.4 // indirect
 	github.com/stretchr/objx v0.5.2 // indirect
 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
-	golang.org/x/net v0.23.0 // indirect
-	golang.org/x/sys v0.18.0 // indirect
-	golang.org/x/term v0.18.0 // indirect
+	github.com/zeebo/xxh3 v1.0.2 // indirect
+	golang.org/x/mod v0.17.0 // indirect
+	golang.org/x/net v0.24.0 // indirect
+	golang.org/x/sync v0.7.0 // indirect
+	golang.org/x/sys v0.20.0 // indirect
+	golang.org/x/term v0.19.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/tools v0.20.0 // indirect
+	golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
+	google.golang.org/grpc v1.63.2 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 289b8ed..8f16c6d 100644
--- a/go.sum
+++ b/go.sum
@@ -6,6 +6,8 @@ atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
 atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
 atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
 atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
+github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU=
+github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
 github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
 github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
 github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
@@ -15,27 +17,33 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/
 github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
 github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
 github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
+github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
+github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
+github.com/apache/arrow/go/v16 v16.0.0 h1:qRLbJRPj4zaseZrjbDHa7mUoZDDIU+4pu+mE2Lucs5g=
+github.com/apache/arrow/go/v16 v16.0.0/go.mod h1:9wnc9mn6vEDTRIm4+27pEjQpRKuTvBaessPoEXQzxWA=
+github.com/apache/thrift v0.20.0 h1:631+KvYbsBZxmuJjYwhezVsrfc/TbqtZV4QcxOX1fOI=
+github.com/apache/thrift v0.20.0/go.mod h1:hOk1BQqcp2OLzGsyVXdfMk7YFlMxK3aoEVhjD06QhB8=
 github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
 github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo=
 github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
-github.com/aws/aws-sdk-go-v2/config v1.27.15 h1:uNnGLZ+DutuNEkuPh6fwqK7LpEiPmzb7MIMA1mNWEUc=
-github.com/aws/aws-sdk-go-v2/config v1.27.15/go.mod h1:7j7Kxx9/7kTmL7z4LlhwQe63MYEE5vkVV6nWg4ZAI8M=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.15 h1:YDexlvDRCA8ems2T5IP1xkMtOZ1uLJOCJdTr0igs5zo=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.15/go.mod h1:vxHggqW6hFNaeNC0WyXS3VdyjcV0a4KMUY4dKJ96buU=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
+github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o=
+github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.12 h1:PVbKQ0KjDosI5+nEdRMU8ygEQDmkJTSHBqPjEX30lqc=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.12/go.mod h1:jlWtGFRtKsqc5zqerHZYmKmRkUXo3KPM14YJ13ZEjwE=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 h1:/FUtT3xsoHO3cfh+I/kCbcMCN98QZRsiFet/V8QkWSs=
-github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7/go.mod h1:MaCAgWpGooQoCWZnMur97rGn5dp350w2+CeiV5406wE=
-github.com/aws/aws-sdk-go-v2/service/glue v1.81.1 h1:+rtgF25rrZ5O5J7k8u3xQcFWAmMQxQWvcyaz1waFj38=
-github.com/aws/aws-sdk-go-v2/service/glue v1.81.1/go.mod h1:NVgCBCXL2/W4WJbFhKUGATTBiWKkYuZyvuh7BsFvZzE=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 h1:5oE2WzJE56/mVveuDZPJESKlg/00AaS2pY2QZcnxg4M=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10/go.mod h1:FHbKWQtRBYUz4vO5WBWjzMD2by126ny5y/1EoaWoLfI=
+github.com/aws/aws-sdk-go-v2/service/glue v1.73.1 h1:z/NBYW8RygzWrDgNWib10fuLUBl0SLj0KruGoEHxnKQ=
+github.com/aws/aws-sdk-go-v2/service/glue v1.73.1/go.mod h1:F3B9DC5FsIHAxUtHZdY5KUeqN+tHoGlRPzSSYdXjC38=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 h1:UXqEWQI0n+q0QixzU0yUUQBZXRd5037qdInTIHFTl98=
@@ -44,8 +52,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJ
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y=
 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 h1:uO5XR6QGBcmPyo2gxofYJLFkcVQ4izOoGDNenlZhTEk=
 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7/go.mod h1:feeeAYfAcwTReM6vbwjEyDmiGho+YgBhaFULuXDW8kc=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3 h1:57NtjG+WLims0TxIQbjTqebZUKDM03DfM11ANAekW0s=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3/go.mod h1:739CllldowZiPPsDFcJHNF4FXrVxaSGVnZ9Ez9Iz9hc=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU=
 github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 h1:Kv1hwNG6jHC/sxMTe5saMjH6t6ZLkgfvVxyEjfWL1ks=
 github.com/aws/aws-sdk-go-v2/service/sso v1.20.8/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA=
 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 h1:nWBZ1xHCF+A7vv9sDzJOq4NWIdzFYm0kH7Pr4OjHYsQ=
@@ -61,8 +69,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI=
+github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -70,27 +84,34 @@ github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQ
 github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
 github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
 github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
-github.com/hamba/avro/v2 v2.22.0 h1:IaBMFv5xmjo38f0oaP9jZiJFXg+lmHPPg7d9YotMnPg=
-github.com/hamba/avro/v2 v2.22.0/go.mod h1:HOeTrE3kvWnBAgsufqhAzDDV5gvS0QXs65Z6BHfGgbg=
+github.com/hamba/avro/v2 v2.21.1 h1:400/jTdLWQ3ib58y83VXlTJKijRouYQszY1SO0cMGt4=
+github.com/hamba/avro/v2 v2.21.1/go.mod h1:ouJ4PkiAEP49u0lAtQyd5Gv04MehKj+7lXwD3zpLpY0=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
+github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
 github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
 github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
-github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
-github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
 github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
 github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
+github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
+github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
+github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -98,6 +119,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
+github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
@@ -107,8 +130,8 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej
 github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
 github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
 github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
-github.com/pterm/pterm v0.12.79 h1:lH3yrYMhdpeqX9y5Ep1u7DejyHy7NSQg9qrBjF9dFT4=
-github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo=
+github.com/pterm/pterm v0.12.78 h1:QTWKaIAa4B32GKwqVXtu9m1DUMgWw3VRljMkMevX+b8=
+github.com/pterm/pterm v0.12.78/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
 github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -129,21 +152,29 @@ github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1z
 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
+github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
+github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
+github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
+golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
+golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -154,15 +185,15 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -174,7 +205,19 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
+golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
+golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
+gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
+gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
+google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
+google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/literals.go b/literals.go
new file mode 100644
index 0000000..9b42e59
--- /dev/null
+++ b/literals.go
@@ -0,0 +1,780 @@
+// 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 iceberg
+
+import (
+	"bytes"
+	"cmp"
+	"errors"
+	"fmt"
+	"math"
+	"reflect"
+	"strconv"
+	"time"
+
+	"github.com/apache/arrow/go/v16/arrow"
+	"github.com/apache/arrow/go/v16/arrow/decimal128"
+	"github.com/google/uuid"
+)
+
+// LiteralType is a generic type constraint for the explicit Go types that we allow
+// for literal values. This represents the actual primitive types that exist in Iceberg
+type LiteralType interface {
+	bool | int32 | int64 | float32 | float64 | Date |
+		Time | Timestamp | string | []byte | uuid.UUID | Decimal
+}
+
+// Comparator is a comparison function for specific literal types:
+//
+//	returns 0 if v1 == v2
+//	returns <0 if v1 < v2
+//	returns >0 if v1 > v2
+type Comparator[T LiteralType] func(v1, v2 T) int
+
+// Literal is a non-null literal value. It can be casted using To and be checked for
+// equality against other literals.
+type Literal interface {
+	fmt.Stringer
+
+	Type() Type
+	To(Type) (Literal, error)
+	Equals(Literal) bool
+}
+
+// TypedLiteral is a generic interface for Literals so that you can retrieve the value.
+// This is based on the physical representative type, which means that FixedLiteral and
+// BinaryLiteral will both return []byte, etc.
+type TypedLiteral[T LiteralType] interface {
+	Literal
+
+	Value() T
+	Comparator() Comparator[T]
+}
+
+// NewLiteral provides a literal based on the type of T
+func NewLiteral[T LiteralType](val T) Literal {
+	switch v := any(val).(type) {
+	case bool:
+		return BoolLiteral(v)
+	case int32:
+		return Int32Literal(v)
+	case int64:
+		return Int64Literal(v)
+	case float32:
+		return Float32Literal(v)
+	case float64:
+		return Float64Literal(v)
+	case Date:
+		return DateLiteral(v)
+	case Time:
+		return TimeLiteral(v)
+	case Timestamp:
+		return TimestampLiteral(v)
+	case string:
+		return StringLiteral(v)
+	case []byte:
+		return BinaryLiteral(v)
+	case uuid.UUID:
+		return UUIDLiteral(v)
+	case Decimal:
+		return DecimalLiteral(v)
+	}
+	panic("can't happen due to literal type constraint")
+}
+
+// convenience to avoid repreating this pattern for primitive types
+func literalEq[L interface {
+	comparable
+	LiteralType
+}, T TypedLiteral[L]](lhs T, other Literal) bool {
+	rhs, ok := other.(T)
+	if !ok {
+		return false
+	}
+
+	return lhs.Value() == rhs.Value()
+}
+
+// AboveMaxLiteral represents values that are above the maximum for their type
+// such as values > math.MaxInt32 for an Int32Literal
+type AboveMaxLiteral interface {
+	Literal
+
+	aboveMax()
+}
+
+// BelowMinLiteral represents values that are below the minimum for their type
+// such as values < math.MinInt32 for an Int32Literal
+type BelowMinLiteral interface {
+	Literal
+
+	belowMin()
+}
+
+type aboveMaxLiteral[T int32 | int64 | float32 | float64] struct {
+	value T
+}
+
+func (ab aboveMaxLiteral[T]) aboveMax() {}
+
+func (ab aboveMaxLiteral[T]) Type() Type {
+	var z T
+	switch any(z).(type) {
+	case int32:
+		return PrimitiveTypes.Int32
+	case int64:
+		return PrimitiveTypes.Int64
+	case float32:
+		return PrimitiveTypes.Float32
+	case float64:
+		return PrimitiveTypes.Float64
+	default:
+		panic("should never happen")
+	}
+}
+
+func (ab aboveMaxLiteral[T]) To(t Type) (Literal, error) {
+	if ab.Type().Equals(t) {
+		return ab, nil
+	}
+	return nil, fmt.Errorf("%w: cannot change type of AboveMax%sLiteral",
+		ErrBadCast, reflect.TypeOf(T(0)).String())
+}
+
+func (ab aboveMaxLiteral[T]) Value() T { return ab.value }
+
+func (ab aboveMaxLiteral[T]) String() string { return "AboveMax" }
+func (ab aboveMaxLiteral[T]) Equals(other Literal) bool {
+	// AboveMaxLiteral isn't comparable and thus isn't even equal to itself
+	return false
+}
+
+type belowMinLiteral[T int32 | int64 | float32 | float64] struct {
+	value T
+}
+
+func (bm belowMinLiteral[T]) belowMin() {}
+
+func (bm belowMinLiteral[T]) Type() Type {
+	var z T
+	switch any(z).(type) {
+	case int32:
+		return PrimitiveTypes.Int32
+	case int64:
+		return PrimitiveTypes.Int64
+	case float32:
+		return PrimitiveTypes.Float32
+	case float64:
+		return PrimitiveTypes.Float64
+	default:
+		panic("should never happen")
+	}
+}
+
+func (bm belowMinLiteral[T]) To(t Type) (Literal, error) {
+	if bm.Type().Equals(t) {
+		return bm, nil
+	}
+	return nil, fmt.Errorf("%w: cannot change type of BelowMin%sLiteral",
+		ErrBadCast, reflect.TypeOf(T(0)).String())
+}
+
+func (bm belowMinLiteral[T]) Value() T { return bm.value }
+
+func (bm belowMinLiteral[T]) String() string { return "BelowMin" }
+func (bm belowMinLiteral[T]) Equals(other Literal) bool {
+	// BelowMinLiteral isn't comparable and thus isn't even equal to itself
+	return false
+}
+
+func Int32AboveMaxLiteral() Literal {
+	return aboveMaxLiteral[int32]{value: math.MaxInt32}
+}
+
+func Int64AboveMaxLiteral() Literal {
+	return aboveMaxLiteral[int64]{value: math.MaxInt64}
+}
+
+func Float32AboveMaxLiteral() Literal {
+	return aboveMaxLiteral[float32]{value: math.MaxFloat32}
+}
+
+func Float64AboveMaxLiteral() Literal {
+	return aboveMaxLiteral[float64]{value: math.MaxFloat64}
+}
+
+func Int32BelowMinLiteral() Literal {
+	return belowMinLiteral[int32]{value: math.MinInt32}
+}
+
+func Int64BelowMinLiteral() Literal {
+	return belowMinLiteral[int64]{value: math.MinInt64}
+}
+
+func Float32BelowMinLiteral() Literal {
+	return belowMinLiteral[float32]{value: -math.MaxFloat32}
+}
+
+func Float64BelowMinLiteral() Literal {
+	return belowMinLiteral[float64]{value: -math.MaxFloat64}
+}
+
+type BoolLiteral bool
+
+func (BoolLiteral) Comparator() Comparator[bool] {
+	return func(v1, v2 bool) int {
+		if v1 {
+			if v2 {
+				return 0
+			}
+			return 1
+		}
+		return -1
+	}
+}
+
+func (b BoolLiteral) Type() Type     { return PrimitiveTypes.Bool }
+func (b BoolLiteral) Value() bool    { return bool(b) }
+func (b BoolLiteral) String() string { return strconv.FormatBool(bool(b)) }
+func (b BoolLiteral) To(t Type) (Literal, error) {
+	switch t.(type) {
+	case BooleanType:
+		return b, nil
+	}
+	return nil, fmt.Errorf("%w: BoolLiteral to %s", ErrBadCast, t)
+}
+
+func (b BoolLiteral) Equals(l Literal) bool {
+	return literalEq(b, l)
+}
+
+type Int32Literal int32
+
+func (Int32Literal) Comparator() Comparator[int32] { return cmp.Compare[int32] }
+func (i Int32Literal) Type() Type                  { return PrimitiveTypes.Int32 }
+func (i Int32Literal) Value() int32                { return int32(i) }
+func (i Int32Literal) String() string              { return strconv.FormatInt(int64(i), 10) }
+func (i Int32Literal) To(t Type) (Literal, error) {
+	switch t := t.(type) {
+	case Int32Type:
+		return i, nil
+	case Int64Type:
+		return Int64Literal(i), nil
+	case Float32Type:
+		return Float32Literal(i), nil
+	case Float64Type:
+		return Float64Literal(i), nil
+	case DateType:
+		return DateLiteral(i), nil
+	case TimeType:
+		return TimeLiteral(i), nil
+	case TimestampType:
+		return TimestampLiteral(i), nil
+	case TimestampTzType:
+		return TimestampLiteral(i), nil
+	case DecimalType:
+		unscaled := Decimal{Val: decimal128.FromI64(int64(i)), Scale: 0}
+		if t.scale == 0 {
+			return DecimalLiteral(unscaled), nil
+		}
+		out, err := unscaled.Val.Rescale(0, int32(t.scale))
+		if err != nil {
+			return nil, fmt.Errorf("%w: failed to cast to DecimalType: %s", ErrBadCast, err.Error())
+		}
+		return DecimalLiteral{Val: out, Scale: t.scale}, nil
+	}
+
+	return nil, fmt.Errorf("%w: Int32Literal to %s", ErrBadCast, t)
+}
+
+func (i Int32Literal) Equals(other Literal) bool {
+	return literalEq(i, other)
+}
+
+type Int64Literal int64
+
+func (Int64Literal) Comparator() Comparator[int64] { return cmp.Compare[int64] }
+func (i Int64Literal) Type() Type                  { return PrimitiveTypes.Int64 }
+func (i Int64Literal) Value() int64                { return int64(i) }
+func (i Int64Literal) String() string              { return strconv.FormatInt(int64(i), 10) }
+func (i Int64Literal) To(t Type) (Literal, error) {
+	switch t := t.(type) {
+	case Int32Type:
+		if math.MaxInt32 < i {
+			return Int32AboveMaxLiteral(), nil
+		} else if math.MinInt32 > i {
+			return Int32BelowMinLiteral(), nil
+		}
+		return Int32Literal(i), nil
+	case Int64Type:
+		return i, nil
+	case Float32Type:
+		return Float32Literal(i), nil
+	case Float64Type:
+		return Float64Literal(i), nil
+	case DateType:
+		return DateLiteral(i), nil
+	case TimeType:
+		return TimeLiteral(i), nil
+	case TimestampType:
+		return TimestampLiteral(i), nil
+	case TimestampTzType:
+		return TimestampLiteral(i), nil
+	case DecimalType:
+		unscaled := Decimal{Val: decimal128.FromI64(int64(i)), Scale: 0}
+		if t.scale == 0 {
+			return DecimalLiteral(unscaled), nil
+		}
+		out, err := unscaled.Val.Rescale(0, int32(t.scale))
+		if err != nil {
+			return nil, fmt.Errorf("%w: failed to cast to DecimalType: %s", ErrBadCast, err.Error())
+		}
+		return DecimalLiteral{Val: out, Scale: t.scale}, nil
+	}
+
+	return nil, fmt.Errorf("%w: Int64Literal to %s", ErrBadCast, t)
+}
+func (i Int64Literal) Equals(other Literal) bool {
+	return literalEq(i, other)
+}
+
+type Float32Literal float32
+
+func (Float32Literal) Comparator() Comparator[float32] { return cmp.Compare[float32] }
+func (f Float32Literal) Type() Type                    { return PrimitiveTypes.Float32 }
+func (f Float32Literal) Value() float32                { return float32(f) }
+func (f Float32Literal) String() string                { return strconv.FormatFloat(float64(f), 'g', -1, 32) }
+func (f Float32Literal) To(t Type) (Literal, error) {
+	switch t := t.(type) {
+	case Float32Type:
+		return f, nil
+	case Float64Type:
+		return Float64Literal(f), nil
+	case DecimalType:
+		v, err := decimal128.FromFloat32(float32(f), int32(t.precision), int32(t.scale))
+		if err != nil {
+			return nil, err
+		}
+		return DecimalLiteral{Val: v, Scale: t.scale}, nil
+	}
+
+	return nil, fmt.Errorf("%w: Float32Literal to %s", ErrBadCast, t)
+}
+func (f Float32Literal) Equals(other Literal) bool {
+	return literalEq(f, other)
+}
+
+type Float64Literal float64
+
+func (Float64Literal) Comparator() Comparator[float64] { return cmp.Compare[float64] }
+func (f Float64Literal) Type() Type                    { return PrimitiveTypes.Float64 }
+func (f Float64Literal) Value() float64                { return float64(f) }
+func (f Float64Literal) String() string                { return strconv.FormatFloat(float64(f), 'g', -1, 64) }
+func (f Float64Literal) To(t Type) (Literal, error) {
+	switch t := t.(type) {
+	case Float32Type:
+		if math.MaxFloat32 < f {
+			return Float32AboveMaxLiteral(), nil
+		} else if -math.MaxFloat32 > f {
+			return Float32BelowMinLiteral(), nil
+		}
+		return Float32Literal(f), nil
+	case Float64Type:
+		return f, nil
+	case DecimalType:
+		v, err := decimal128.FromFloat64(float64(f), int32(t.precision), int32(t.scale))
+		if err != nil {
+			return nil, err
+		}
+		return DecimalLiteral{Val: v, Scale: t.scale}, nil
+	}
+
+	return nil, fmt.Errorf("%w: Float64Literal to %s", ErrBadCast, t)
+}
+func (f Float64Literal) Equals(other Literal) bool {
+	return literalEq(f, other)
+}
+
+type DateLiteral Date
+
+func (DateLiteral) Comparator() Comparator[Date] { return cmp.Compare[Date] }
+func (d DateLiteral) Type() Type                 { return PrimitiveTypes.Date }
+func (d DateLiteral) Value() Date                { return Date(d) }
+func (d DateLiteral) String() string {
+	t := time.Unix(0, 0).UTC().AddDate(0, 0, int(d))
+	return t.Format("2006-01-02")
+}
+func (d DateLiteral) To(t Type) (Literal, error) {
+	switch t.(type) {
+	case DateType:
+		return d, nil
+	}
+	return nil, fmt.Errorf("%w: DateLiteral to %s", ErrBadCast, t)
+}
+func (d DateLiteral) Equals(other Literal) bool {
+	return literalEq(d, other)
+}
+
+type TimeLiteral Time
+
+func (TimeLiteral) Comparator() Comparator[Time] { return cmp.Compare[Time] }
+func (t TimeLiteral) Type() Type                 { return PrimitiveTypes.Time }
+func (t TimeLiteral) Value() Time                { return Time(t) }
+func (t TimeLiteral) String() string {
+	tm := time.UnixMicro(int64(t)).UTC()
+	return tm.Format("15:04:05.000000")
+}
+func (t TimeLiteral) To(typ Type) (Literal, error) {
+	switch typ.(type) {
+	case TimeType:
+		return t, nil
+	}
+	return nil, fmt.Errorf("%w: TimeLiteral to %s", ErrBadCast, typ)
+
+}
+func (t TimeLiteral) Equals(other Literal) bool {
+	return literalEq(t, other)
+}
+
+type TimestampLiteral Timestamp
+
+func (TimestampLiteral) Comparator() Comparator[Timestamp] { return cmp.Compare[Timestamp] }
+func (t TimestampLiteral) Type() Type                      { return PrimitiveTypes.Timestamp }
+func (t TimestampLiteral) Value() Timestamp                { return Timestamp(t) }
+func (t TimestampLiteral) String() string {
+	tm := time.UnixMicro(int64(t)).UTC()
+	return tm.Format("2006-01-02 15:04:05.000000")
+}
+func (t TimestampLiteral) To(typ Type) (Literal, error) {
+	switch typ.(type) {
+	case TimestampType:
+		return t, nil
+	case TimestampTzType:
+		return t, nil
+	case DateType:
+		return DateLiteral(Timestamp(t).ToDate()), nil
+	}
+	return nil, fmt.Errorf("%w: TimestampLiteral to %s", ErrBadCast, typ)
+}
+func (t TimestampLiteral) Equals(other Literal) bool {
+	return literalEq(t, other)
+}
+
+type StringLiteral string
+
+func (StringLiteral) Comparator() Comparator[string] { return cmp.Compare[string] }
+func (s StringLiteral) Type() Type                   { return PrimitiveTypes.String }
+func (s StringLiteral) Value() string                { return string(s) }
+func (s StringLiteral) String() string               { return string(s) }
+func (s StringLiteral) To(typ Type) (Literal, error) {
+	switch t := typ.(type) {
+	case StringType:
+		return s, nil
+	case Int32Type:
+		n, err := strconv.ParseInt(string(s), 10, 64)
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s",
+				errors.Join(ErrBadCast, err), s, typ)
+		}
+
+		if math.MaxInt32 < n {
+			return Int32AboveMaxLiteral(), nil
+		} else if math.MinInt32 > n {
+			return Int32BelowMinLiteral(), nil
+		}
+
+		return Int32Literal(n), nil
+	case Int64Type:
+		n, err := strconv.ParseInt(string(s), 10, 64)
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s",
+				errors.Join(ErrBadCast, err), s, typ)
+		}
+
+		return Int64Literal(n), nil
+	case Float32Type:
+		n, err := strconv.ParseFloat(string(s), 32)
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s",
+				errors.Join(ErrBadCast, err), s, typ)
+		}
+		return Float32Literal(n), nil
+	case Float64Type:
+		n, err := strconv.ParseFloat(string(s), 64)
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s",
+				errors.Join(ErrBadCast, err), s, typ)
+		}
+		return Float64Literal(n), nil
+	case DateType:
+		tm, err := time.Parse("2006-01-02", string(s))
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s - %s",
+				ErrBadCast, s, typ, err.Error())
+		}
+		return DateLiteral(tm.Truncate(24*time.Hour).Unix() / int64((time.Hour * 24).Seconds())), nil
+	case TimeType:
+		val, err := arrow.Time64FromString(string(s), arrow.Microsecond)
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s - %s",
+				ErrBadCast, s, typ, err.Error())
+		}
+
+		return TimeLiteral(val), nil
+	case TimestampType:
+		// requires RFC3339 with no time zone
+		tm, err := time.Parse("2006-01-02T15:04:05", string(s))
+		if err != nil {
+			return nil, fmt.Errorf("%w: invalid Timestamp format for casting from string '%s': %s",
+				ErrBadCast, s, err.Error())
+		}
+
+		return TimestampLiteral(Timestamp(tm.UTC().UnixMicro())), nil
+	case TimestampTzType:
+		// requires RFC3339 format WITH time zone
+		tm, err := time.Parse(time.RFC3339, string(s))
+		if err != nil {
+			return nil, fmt.Errorf("%w: invalid TimestampTz format for casting from string '%s': %s",
+				ErrBadCast, s, err.Error())
+		}
+
+		return TimestampLiteral(Timestamp(tm.UTC().UnixMicro())), nil
+	case UUIDType:
+		val, err := uuid.Parse(string(s))
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s - %s",
+				ErrBadCast, s, typ, err.Error())
+		}
+		return UUIDLiteral(val), nil
+	case DecimalType:
+		n, err := decimal128.FromString(string(s), int32(t.precision), int32(t.scale))
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s - %s",
+				ErrBadCast, s, typ, err.Error())
+		}
+		return DecimalLiteral{Val: n, Scale: t.scale}, nil
+	case BooleanType:
+		val, err := strconv.ParseBool(string(s))
+		if err != nil {
+			return nil, fmt.Errorf("%w: casting '%s' to %s - %s",
+				ErrBadCast, s, typ, err.Error())
+		}
+		return BoolLiteral(val), nil
+	}
+	return nil, fmt.Errorf("%w: StringLiteral to %s", ErrBadCast, typ)
+}
+
+func (s StringLiteral) Equals(other Literal) bool {
+	return literalEq(s, other)
+}
+
+type BinaryLiteral []byte
+
+func (BinaryLiteral) Comparator() Comparator[[]byte] {
+	return bytes.Compare
+}
+func (b BinaryLiteral) Type() Type     { return PrimitiveTypes.Binary }
+func (b BinaryLiteral) Value() []byte  { return []byte(b) }
+func (b BinaryLiteral) String() string { return string(b) }
+func (b BinaryLiteral) To(typ Type) (Literal, error) {
+	switch t := typ.(type) {
+	case UUIDType:
+		val, err := uuid.FromBytes(b)
+		if err != nil {
+			return nil, fmt.Errorf("%w: cannot convert BinaryLiteral to UUID",
+				errors.Join(ErrBadCast, err))
+		}
+		return UUIDLiteral(val), nil
+	case FixedType:
+		if len(b) == t.len {
+			return FixedLiteral(b), nil
+		}
+
+		return nil, fmt.Errorf("%w: cannot convert BinaryLiteral to %s, different length - %d <> %d",
+			ErrBadCast, typ, len(b), t.len)
+	case BinaryType:
+		return b, nil
+	}
+
+	return nil, fmt.Errorf("%w: BinaryLiteral to %s", ErrBadCast, typ)
+}
+func (b BinaryLiteral) Equals(other Literal) bool {
+	rhs, ok := other.(BinaryLiteral)
+	if !ok {
+		return false
+	}
+
+	return bytes.Equal([]byte(b), rhs)
+}
+
+type FixedLiteral []byte
+
+func (FixedLiteral) Comparator() Comparator[[]byte] { return bytes.Compare }
+func (f FixedLiteral) Type() Type                   { return FixedTypeOf(len(f)) }
+func (f FixedLiteral) Value() []byte                { return []byte(f) }
+func (f FixedLiteral) String() string               { return string(f) }
+func (f FixedLiteral) To(typ Type) (Literal, error) {
+	switch t := typ.(type) {
+	case UUIDType:
+		val, err := uuid.FromBytes(f)
+		if err != nil {
+			return nil, fmt.Errorf("%w: cannot convert FixedLiteral to UUID - %s",
+				ErrBadCast, err.Error())
+		}
+		return UUIDLiteral(val), nil
+	case FixedType:
+		if len(f) == t.len {
+			return FixedLiteral(f), nil
+		}
+
+		return nil, fmt.Errorf("%w: cannot convert FixedLiteral to %s, different length - %d <> %d",
+			ErrBadCast, typ, len(f), t.len)
+	case BinaryType:
+		return f, nil
+	}
+
+	return nil, fmt.Errorf("%w: FixedLiteral[%d] to %s",
+		ErrBadCast, len(f), typ)
+}
+func (f FixedLiteral) Equals(other Literal) bool {
+	rhs, ok := other.(FixedLiteral)
+	if !ok {
+		return false
+	}
+
+	return bytes.Equal([]byte(f), rhs)
+}
+
+type UUIDLiteral uuid.UUID
+
+func (UUIDLiteral) Comparator() Comparator[uuid.UUID] {
+	return func(v1, v2 uuid.UUID) int {
+		return bytes.Compare(v1[:], v2[:])
+	}
+}
+
+func (UUIDLiteral) Type() Type         { return PrimitiveTypes.UUID }
+func (u UUIDLiteral) Value() uuid.UUID { return uuid.UUID(u) }
+func (u UUIDLiteral) String() string   { return uuid.UUID(u).String() }
+func (u UUIDLiteral) To(typ Type) (Literal, error) {
+	switch t := typ.(type) {
+	case UUIDType:
+		return u, nil
+	case FixedType:
+		if len(u) == t.len {
+			v, _ := uuid.UUID(u).MarshalBinary()
+			return FixedLiteral(v), nil
+		}
+
+		return nil, fmt.Errorf("%w: cannot convert UUIDLiteral to %s, different length - %d <> %d",
+			ErrBadCast, typ, len(u), t.len)
+	case BinaryType:
+		v, _ := uuid.UUID(u).MarshalBinary()
+		return BinaryLiteral(v), nil
+	}
+
+	return nil, fmt.Errorf("%w: UUIDLiteral to %s", ErrBadCast, typ)
+}
+func (u UUIDLiteral) Equals(other Literal) bool {
+	rhs, ok := other.(UUIDLiteral)
+	if !ok {
+		return false
+	}
+
+	return uuid.UUID(u) == uuid.UUID(rhs)
+}
+
+type DecimalLiteral Decimal
+
+func (DecimalLiteral) Comparator() Comparator[Decimal] {
+	return func(v1, v2 Decimal) int {
+		if v1.Scale == v2.Scale {
+			return v1.Val.Cmp(v2.Val)
+		}
+
+		rescaled, err := v2.Val.Rescale(int32(v2.Scale), int32(v1.Scale))
+		if err != nil {
+			return -1
+		}
+
+		return v1.Val.Cmp(rescaled)
+	}
+}
+func (d DecimalLiteral) Type() Type     { return DecimalTypeOf(9, d.Scale) }
+func (d DecimalLiteral) Value() Decimal { return Decimal(d) }
+func (d DecimalLiteral) String() string {
+	return d.Val.ToString(int32(d.Scale))
+}
+
+func (d DecimalLiteral) To(t Type) (Literal, error) {
+	switch t := t.(type) {
+	case DecimalType:
+		if d.Scale == t.scale {
+			return d, nil
+		}
+
+		return nil, fmt.Errorf("%w: could not convert %v to %s",
+			ErrBadCast, d, t)
+	case Int32Type:
+		v := d.Val.BigInt().Int64()
+		if v > math.MaxInt32 {
+			return Int32AboveMaxLiteral(), nil
+		} else if v < math.MinInt32 {
+			return Int32BelowMinLiteral(), nil
+		}
+
+		return Int32Literal(int32(v)), nil
+	case Int64Type:
+		v := d.Val.BigInt()
+		if !v.IsInt64() {
+			if v.Sign() > 0 {
+				return Int64AboveMaxLiteral(), nil
+			} else if v.Sign() < 0 {
+				return Int64BelowMinLiteral(), nil
+			}
+		}
+
+		return Int64Literal(v.Int64()), nil
+	case Float32Type:
+		v := d.Val.ToFloat64(int32(d.Scale))
+		if v > math.MaxFloat32 {
+			return Float32AboveMaxLiteral(), nil
+		} else if v < -math.MaxFloat32 {
+			return Float32BelowMinLiteral(), nil
+		}
+
+		return Float32Literal(float32(v)), nil
+	case Float64Type:
+		return Float64Literal(d.Val.ToFloat64(int32(d.Scale))), nil
+	}
+
+	return nil, fmt.Errorf("%w: DecimalLiteral to %s", ErrBadCast, t)
+}
+
+func (d DecimalLiteral) Equals(other Literal) bool {
+	rhs, ok := other.(DecimalLiteral)
+	if !ok {
+		return false
+	}
+
+	rescaled, err := rhs.Val.Rescale(int32(rhs.Scale), int32(d.Scale))
+	if err != nil {
+		return false
+	}
+	return d.Val == rescaled
+}
diff --git a/literals_test.go b/literals_test.go
new file mode 100644
index 0000000..f7a483b
--- /dev/null
+++ b/literals_test.go
@@ -0,0 +1,729 @@
+// 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 iceberg_test
+
+import (
+	"math"
+	"strconv"
+	"testing"
+	"time"
+
+	"github.com/apache/arrow/go/v16/arrow"
+	"github.com/apache/arrow/go/v16/arrow/decimal128"
+	"github.com/apache/iceberg-go"
+	"github.com/google/uuid"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNumericLiteralCompare(t *testing.T) {
+	smallLit := iceberg.NewLiteral(int32(10)).(iceberg.Int32Literal)
+	bigLit := iceberg.NewLiteral(int32(1000)).(iceberg.Int32Literal)
+
+	assert.False(t, smallLit.Equals(bigLit))
+	assert.True(t, smallLit.Equals(iceberg.NewLiteral(int32(10))))
+
+	cmp := smallLit.Comparator()
+
+	assert.Equal(t, -1, cmp(smallLit.Value(), bigLit.Value()))
+	assert.Equal(t, 1, cmp(bigLit.Value(), smallLit.Value()))
+	assert.True(t, smallLit.Type().Equals(iceberg.PrimitiveTypes.Int32))
+}
+
+func TestIntConversion(t *testing.T) {
+	lit := iceberg.NewLiteral(int32(34))
+
+	t.Run("to int64", func(t *testing.T) {
+		longLit, err := lit.To(iceberg.PrimitiveTypes.Int64)
+		assert.NoError(t, err)
+		assert.IsType(t, iceberg.Int64Literal(0), longLit)
+		assert.EqualValues(t, 34, longLit)
+	})
+
+	t.Run("to float32", func(t *testing.T) {
+		floatLit, err := lit.To(iceberg.PrimitiveTypes.Float32)
+		assert.NoError(t, err)
+		assert.IsType(t, iceberg.Float32Literal(0), floatLit)
+		assert.EqualValues(t, 34, floatLit)
+	})
+
+	t.Run("to float64", func(t *testing.T) {
+		dblLit, err := lit.To(iceberg.PrimitiveTypes.Float64)
+		assert.NoError(t, err)
+		assert.IsType(t, iceberg.Float64Literal(0), dblLit)
+		assert.EqualValues(t, 34, dblLit)
+	})
+}
+
+func TestIntToDecimalConversion(t *testing.T) {
+	tests := []struct {
+		ty  iceberg.DecimalType
+		val iceberg.Decimal
+	}{
+		{iceberg.DecimalTypeOf(9, 0),
+			iceberg.Decimal{Val: decimal128.FromI64(34), Scale: 0}},
+		{iceberg.DecimalTypeOf(9, 2),
+			iceberg.Decimal{Val: decimal128.FromI64(3400), Scale: 2}},
+		{iceberg.DecimalTypeOf(9, 4),
+			iceberg.Decimal{Val: decimal128.FromI64(340000), Scale: 4}},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.ty.String(), func(t *testing.T) {
+			lit := iceberg.Int32Literal(34)
+
+			dec, err := lit.To(tt.ty)
+			require.NoError(t, err)
+			assert.IsType(t, iceberg.DecimalLiteral(tt.val), dec)
+			assert.EqualValues(t, tt.val, dec)
+		})
+	}
+}
+
+func TestIntToDateConversion(t *testing.T) {
+	oneDay, _ := time.Parse("2006-01-02", "2022-03-28")
+	val := int32(arrow.Date32FromTime(oneDay))
+	dateLit, err := iceberg.NewLiteral(val).To(iceberg.PrimitiveTypes.Date)
+	require.NoError(t, err)
+	assert.True(t, dateLit.Type().Equals(iceberg.PrimitiveTypes.Date))
+	assert.EqualValues(t, val, dateLit)
+
+	lit := iceberg.Int32Literal(34)
+	tm, err := lit.To(iceberg.PrimitiveTypes.Time)
+	require.NoError(t, err)
+	assert.EqualValues(t, lit, tm)
+
+	tm, err = lit.To(iceberg.PrimitiveTypes.Timestamp)
+	require.NoError(t, err)
+	assert.EqualValues(t, lit, tm)
+
+	tm, err = lit.To(iceberg.PrimitiveTypes.TimestampTz)
+	require.NoError(t, err)
+	assert.EqualValues(t, lit, tm)
+}
+
+func TestInt64Conversions(t *testing.T) {
+	tests := []struct {
+		from iceberg.Int64Literal
+		to   iceberg.Literal
+	}{
+		{iceberg.Int64Literal(34), iceberg.NewLiteral(int32(34))},
+		{iceberg.Int64Literal(34), iceberg.NewLiteral(float32(34))},
+		{iceberg.Int64Literal(34), iceberg.NewLiteral(float64(34))},
+		{iceberg.Int64Literal(19709), iceberg.NewLiteral(iceberg.Date(19709))},
+		{iceberg.Int64Literal(51661919000), iceberg.NewLiteral(iceberg.Time(51661919000))},
+		{iceberg.Int64Literal(1647305201), iceberg.NewLiteral(iceberg.Timestamp(1647305201))},
+		{iceberg.Int64Literal(34),
+			iceberg.NewLiteral(iceberg.Decimal{Val: decimal128.FromI64(34), Scale: 0})},
+		{iceberg.Int64Literal(34),
+			iceberg.NewLiteral(iceberg.Decimal{Val: decimal128.FromI64(3400), Scale: 2})},
+		{iceberg.Int64Literal(34),
+			iceberg.NewLiteral(iceberg.Decimal{Val: decimal128.FromI64(340000), Scale: 4})},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.to.Type().String(), func(t *testing.T) {
+			got, err := tt.from.To(tt.to.Type())
+			require.NoError(t, err)
+			assert.True(t, tt.to.Equals(got))
+		})
+	}
+}
+
+func TestInt64ToInt32OutsideBound(t *testing.T) {
+	bigLit := iceberg.NewLiteral(int64(math.MaxInt32 + 1))
+	aboveMax, err := bigLit.To(iceberg.PrimitiveTypes.Int32)
+	require.NoError(t, err)
+	assert.Implements(t, (*iceberg.AboveMaxLiteral)(nil), aboveMax)
+	assert.Equal(t, iceberg.Int32AboveMaxLiteral(), aboveMax)
+	assert.Equal(t, iceberg.PrimitiveTypes.Int32, aboveMax.Type())
+
+	smallLit := iceberg.NewLiteral(int64(math.MinInt32 - 1))
+	belowMin, err := smallLit.To(iceberg.PrimitiveTypes.Int32)
+	require.NoError(t, err)
+	assert.Implements(t, (*iceberg.BelowMinLiteral)(nil), belowMin)
+	assert.Equal(t, iceberg.Int32BelowMinLiteral(), belowMin)
+	assert.Equal(t, iceberg.PrimitiveTypes.Int32, belowMin.Type())
+}
+
+func TestFloatConversions(t *testing.T) {
+	n1, _ := decimal128.FromFloat32(34.56, 9, 1)
+	n2, _ := decimal128.FromFloat32(34.56, 9, 2)
+	n3, _ := decimal128.FromFloat32(34.56, 9, 4)
+
+	tests := []struct {
+		from iceberg.Float32Literal
+		to   iceberg.Literal
+	}{
+		{iceberg.Float32Literal(34.5), iceberg.NewLiteral(float64(34.5))},
+		{iceberg.Float32Literal(34.56),
+			iceberg.NewLiteral(iceberg.Decimal{Val: n1, Scale: 1})},
+		{iceberg.Float32Literal(34.56),
+			iceberg.NewLiteral(iceberg.Decimal{Val: n2, Scale: 2})},
+		{iceberg.Float32Literal(34.56),
+			iceberg.NewLiteral(iceberg.Decimal{Val: n3, Scale: 4})},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.to.Type().String(), func(t *testing.T) {
+			got, err := tt.from.To(tt.to.Type())
+			require.NoError(t, err)
+			assert.Truef(t, tt.to.Equals(got), "expected: %s, got: %s", tt.to, got)
+		})
+	}
+}
+
+func TestFloat64Conversions(t *testing.T) {
+	n1, _ := decimal128.FromFloat64(34.56, 9, 1)
+	n2, _ := decimal128.FromFloat64(34.56, 9, 2)
+	n3, _ := decimal128.FromFloat64(34.56, 9, 4)
+
+	tests := []struct {
+		from iceberg.Float64Literal
+		to   iceberg.Literal
+	}{
+		{iceberg.Float64Literal(34.5), iceberg.NewLiteral(float32(34.5))},
+		{iceberg.Float64Literal(34.56),
+			iceberg.NewLiteral(iceberg.Decimal{Val: n1, Scale: 1})},
+		{iceberg.Float64Literal(34.56),
+			iceberg.NewLiteral(iceberg.Decimal{Val: n2, Scale: 2})},
+		{iceberg.Float64Literal(34.56),
+			iceberg.NewLiteral(iceberg.Decimal{Val: n3, Scale: 4})},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.to.Type().String(), func(t *testing.T) {
+			got, err := tt.from.To(tt.to.Type())
+			require.NoError(t, err)
+			assert.Truef(t, tt.to.Equals(got), "expected: %s, got: %s", tt.to, got)
+		})
+	}
+}
+
+func TestFloat64toFloat32OutsideBounds(t *testing.T) {
+	bigLit := iceberg.NewLiteral(float64(math.MaxFloat32 + 1.0e37))
+	aboveMax, err := bigLit.To(iceberg.PrimitiveTypes.Float32)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Float32AboveMaxLiteral(), aboveMax)
+
+	smallLit := iceberg.NewLiteral(float64(-math.MaxFloat32 - 1.0e37))
+	belowMin, err := smallLit.To(iceberg.PrimitiveTypes.Float32)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Float32BelowMinLiteral(), belowMin)
+}
+
+func TestDecimalToDecimalConversion(t *testing.T) {
+	lit := iceberg.NewLiteral(iceberg.Decimal{Val: decimal128.FromI64(3411), Scale: 2})
+
+	v, err := lit.To(iceberg.DecimalTypeOf(9, 2))
+	require.NoError(t, err)
+	assert.Equal(t, lit, v)
+
+	v, err = lit.To(iceberg.DecimalTypeOf(11, 2))
+	require.NoError(t, err)
+	assert.Equal(t, lit, v)
+
+	_, err = lit.To(iceberg.DecimalTypeOf(9, 0))
+	assert.ErrorIs(t, err, iceberg.ErrBadCast)
+	assert.ErrorContains(t, err, "could not convert 34.11 to decimal(9, 0)")
+
+	_, err = lit.To(iceberg.DecimalTypeOf(9, 1))
+	assert.ErrorIs(t, err, iceberg.ErrBadCast)
+	assert.ErrorContains(t, err, "could not convert 34.11 to decimal(9, 1)")
+
+	_, err = lit.To(iceberg.DecimalTypeOf(9, 3))
+	assert.ErrorIs(t, err, iceberg.ErrBadCast)
+	assert.ErrorContains(t, err, "could not convert 34.11 to decimal(9, 3)")
+}
+
+func TestDecimalLiteralConversions(t *testing.T) {
+	n1 := iceberg.Decimal{Val: decimal128.FromI64(1234), Scale: 2}
+	n2 := iceberg.Decimal{Val: decimal128.FromI64(math.MaxInt32 + 1), Scale: 0}
+	n3 := iceberg.Decimal{Val: decimal128.FromI64(math.MinInt32 - 1), Scale: 10}
+
+	tests := []struct {
+		from iceberg.DecimalLiteral
+		to   iceberg.Literal
+	}{
+		{iceberg.DecimalLiteral(n1), iceberg.NewLiteral(int32(1234))},
+		{iceberg.DecimalLiteral(n1), iceberg.NewLiteral(int64(1234))},
+		{iceberg.DecimalLiteral(n2), iceberg.NewLiteral(int64(math.MaxInt32 + 1))},
+		{iceberg.DecimalLiteral(n1), iceberg.NewLiteral(float32(12.34))},
+		{iceberg.DecimalLiteral(n1), iceberg.NewLiteral(float64(12.34))},
+		{iceberg.DecimalLiteral(n3), iceberg.NewLiteral(int64(math.MinInt32 - 1))},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.to.Type().String(), func(t *testing.T) {
+			got, err := tt.from.To(tt.to.Type())
+			require.NoError(t, err)
+			assert.Truef(t, tt.to.Equals(got), "expected: %s, got: %s", tt.to, got)
+		})
+	}
+
+	above, err := iceberg.DecimalLiteral(n2).To(iceberg.PrimitiveTypes.Int32)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Int32AboveMaxLiteral(), above)
+	assert.Equal(t, iceberg.PrimitiveTypes.Int32, above.Type())
+
+	below, err := iceberg.DecimalLiteral(n3).To(iceberg.PrimitiveTypes.Int32)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Int32BelowMinLiteral(), below)
+	assert.Equal(t, iceberg.PrimitiveTypes.Int32, below.Type())
+
+	n4 := iceberg.Decimal{Val: decimal128.FromU64(math.MaxInt64 + 1), Scale: 0}
+	n5 := iceberg.Decimal{Val: decimal128.FromU64(math.MaxUint64).Negate(), Scale: 20}
+
+	above, err = iceberg.DecimalLiteral(n4).To(iceberg.PrimitiveTypes.Int64)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Int64AboveMaxLiteral(), above)
+	assert.Equal(t, iceberg.PrimitiveTypes.Int64, above.Type())
+
+	below, err = iceberg.DecimalLiteral(n5).To(iceberg.PrimitiveTypes.Int64)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Int64BelowMinLiteral(), below)
+	assert.Equal(t, iceberg.PrimitiveTypes.Int64, below.Type())
+
+	v, err := decimal128.FromFloat64(math.MaxFloat32+1e37, 38, -1)
+	require.NoError(t, err)
+	above, err = iceberg.DecimalLiteral(iceberg.Decimal{Val: v, Scale: -1}).
+		To(iceberg.PrimitiveTypes.Float32)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Float32AboveMaxLiteral(), above)
+	assert.Equal(t, iceberg.PrimitiveTypes.Float32, above.Type())
+
+	below, err = iceberg.DecimalLiteral(iceberg.Decimal{Val: v.Negate(), Scale: -1}).
+		To(iceberg.PrimitiveTypes.Float32)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Float32BelowMinLiteral(), below)
+	assert.Equal(t, iceberg.PrimitiveTypes.Float32, below.Type())
+}
+
+func TestLiteralTimestampToDate(t *testing.T) {
+	v, _ := arrow.TimestampFromString("1970-01-01T00:00:00.000000+00:00", arrow.Microsecond)
+	tsLit := iceberg.NewLiteral(iceberg.Timestamp(v))
+	dateLit, err := tsLit.To(iceberg.PrimitiveTypes.Date)
+	require.NoError(t, err)
+	assert.Zero(t, dateLit)
+}
+
+func TestStringLiterals(t *testing.T) {
+	sqrt2 := iceberg.NewLiteral("1.414")
+	pi := iceberg.NewLiteral("3.141")
+	piStr := iceberg.StringLiteral("3.141")
+	piDbl := iceberg.NewLiteral(float64(3.141))
+
+	v, err := pi.To(iceberg.PrimitiveTypes.Float64)
+	require.NoError(t, err)
+	assert.Equal(t, piDbl, v)
+
+	assert.False(t, sqrt2.Equals(pi))
+	assert.True(t, pi.Equals(piStr))
+	assert.False(t, pi.Equals(piDbl))
+	assert.Equal(t, "3.141", pi.String())
+
+	cmp := piStr.Comparator()
+	assert.Equal(t, -1, cmp(sqrt2.(iceberg.StringLiteral).Value(), piStr.Value()))
+	assert.Equal(t, 1, cmp(piStr.Value(), sqrt2.(iceberg.StringLiteral).Value()))
+
+	v, err = pi.To(iceberg.PrimitiveTypes.String)
+	require.NoError(t, err)
+	assert.Equal(t, pi, v)
+}
+
+func TestStringLiteralConversion(t *testing.T) {
+	tm, _ := time.Parse("2006-01-02", "2017-08-18")
+	expected := uuid.New()
+
+	tests := []struct {
+		from iceberg.StringLiteral
+		to   iceberg.Literal
+	}{
+		{iceberg.StringLiteral("2017-08-18"),
+			iceberg.NewLiteral(iceberg.Date(arrow.Date32FromTime(tm)))},
+		{iceberg.StringLiteral("14:21:01.919"),
+			iceberg.NewLiteral(iceberg.Time(51661919000))},
+		{iceberg.StringLiteral("2017-08-18T14:21:01.919234"),
+			iceberg.NewLiteral(iceberg.Timestamp(1503066061919234))},
+		{iceberg.StringLiteral(expected.String()), iceberg.NewLiteral(expected)},
+		{iceberg.StringLiteral("34.560"),
+			iceberg.NewLiteral(iceberg.Decimal{Val: decimal128.FromI64(34560), Scale: 3})},
+		{iceberg.StringLiteral("true"), iceberg.NewLiteral(true)},
+		{iceberg.StringLiteral("True"), iceberg.NewLiteral(true)},
+		{iceberg.StringLiteral("false"), iceberg.NewLiteral(false)},
+		{iceberg.StringLiteral("False"), iceberg.NewLiteral(false)},
+		{iceberg.StringLiteral("12345"), iceberg.NewLiteral(int32(12345))},
+		{iceberg.StringLiteral("12345123456"), iceberg.NewLiteral(int64(12345123456))},
+		{iceberg.StringLiteral("3.14"), iceberg.NewLiteral(float32(3.14))},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.to.Type().String(), func(t *testing.T) {
+			got, err := tt.from.To(tt.to.Type())
+			require.NoError(t, err)
+			assert.Truef(t, tt.to.Equals(got), "expected: %s, got: %s", tt.to, got)
+		})
+	}
+
+	lit := iceberg.StringLiteral("2017-08-18T14:21:01.919234-07:00")
+	casted, err := lit.To(iceberg.PrimitiveTypes.TimestampTz)
+	require.NoError(t, err)
+	expectedTimestamp := iceberg.NewLiteral(iceberg.Timestamp(1503091261919234))
+	assert.Truef(t, casted.Equals(expectedTimestamp), "expected: %s, got: %s",
+		expectedTimestamp, casted)
+
+	_, err = lit.To(iceberg.PrimitiveTypes.Timestamp)
+	require.Error(t, err)
+	assert.ErrorIs(t, err, iceberg.ErrBadCast)
+	assert.ErrorContains(t, err, `parsing time "2017-08-18T14:21:01.919234-07:00": extra text: "-07:00"`)
+	assert.ErrorContains(t, err, "invalid Timestamp format for casting from string")
+
+	_, err = iceberg.StringLiteral("2017-08-18T14:21:01.919234").To(iceberg.PrimitiveTypes.TimestampTz)
+	require.Error(t, err)
+	assert.ErrorIs(t, err, iceberg.ErrBadCast)
+	assert.ErrorContains(t, err, `cannot parse "" as "Z07:00"`)
+}
+
+func TestLiteralIdentityConversions(t *testing.T) {
+	fixedLit, _ := iceberg.NewLiteral([]byte{0x01, 0x02, 0x03}).To(iceberg.FixedTypeOf(3))
+
+	tests := []struct {
+		lit iceberg.Literal
+		typ iceberg.PrimitiveType
+	}{
+		{iceberg.NewLiteral(true), iceberg.PrimitiveTypes.Bool},
+		{iceberg.NewLiteral(int32(34)), iceberg.PrimitiveTypes.Int32},
+		{iceberg.NewLiteral(int64(340000000)), iceberg.PrimitiveTypes.Int64},
+		{iceberg.NewLiteral(float32(34.11)), iceberg.PrimitiveTypes.Float32},
+		{iceberg.NewLiteral(float64(3.5028235e38)), iceberg.PrimitiveTypes.Float64},
+		{iceberg.NewLiteral(iceberg.Decimal{Val: decimal128.FromI64(3455), Scale: 2}),
+			iceberg.DecimalTypeOf(9, 2)},
+		{iceberg.NewLiteral(iceberg.Date(19079)), iceberg.PrimitiveTypes.Date},
+		{iceberg.NewLiteral(iceberg.Timestamp(1503091261919234)),
+			iceberg.PrimitiveTypes.Timestamp},
+		{iceberg.NewLiteral("abc"), iceberg.PrimitiveTypes.String},
+		{iceberg.NewLiteral(uuid.New()), iceberg.PrimitiveTypes.UUID},
+		{fixedLit, iceberg.FixedTypeOf(3)},
+		{iceberg.NewLiteral([]byte{0x01, 0x02, 0x03}), iceberg.PrimitiveTypes.Binary},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.typ.String(), func(t *testing.T) {
+			expected, err := tt.lit.To(tt.typ)
+			require.NoError(t, err)
+			assert.Equal(t, expected, tt.lit)
+		})
+	}
+}
+
+func TestFixedLiteral(t *testing.T) {
+	fixedLit012 := iceberg.FixedLiteral{0x00, 0x01, 0x02}
+	fixedLit013 := iceberg.FixedLiteral{0x00, 0x01, 0x03}
+	assert.True(t, fixedLit012.Equals(fixedLit012))
+	assert.False(t, fixedLit012.Equals(fixedLit013))
+
+	cmp := fixedLit012.Comparator()
+	assert.Equal(t, -1, cmp(fixedLit012, fixedLit013))
+	assert.Equal(t, 1, cmp(fixedLit013, fixedLit012))
+	assert.Equal(t, 0, cmp(fixedLit013, fixedLit013))
+
+	testUuid := uuid.New()
+	lit, err := iceberg.NewLiteral(testUuid[:]).To(iceberg.FixedTypeOf(16))
+	require.NoError(t, err)
+	uuidLit, err := lit.To(iceberg.PrimitiveTypes.UUID)
+	require.NoError(t, err)
+
+	assert.EqualValues(t, uuidLit, testUuid)
+
+	fixedUuid, err := uuidLit.To(iceberg.FixedTypeOf(16))
+	require.NoError(t, err)
+	assert.EqualValues(t, testUuid[:], fixedUuid)
+
+	binUuid, err := uuidLit.To(iceberg.PrimitiveTypes.Binary)
+	require.NoError(t, err)
+	assert.EqualValues(t, testUuid[:], binUuid)
+
+	binlit, err := fixedLit012.To(iceberg.PrimitiveTypes.Binary)
+	require.NoError(t, err)
+	assert.EqualValues(t, fixedLit012, binlit)
+}
+
+func TestBinaryLiteral(t *testing.T) {
+	binLit012 := iceberg.NewLiteral([]byte{0x00, 0x01, 0x02}).(iceberg.BinaryLiteral)
+	binLit013 := iceberg.NewLiteral([]byte{0x00, 0x01, 0x03}).(iceberg.BinaryLiteral)
+	assert.True(t, binLit012.Equals(binLit012))
+	assert.False(t, binLit012.Equals(binLit013))
+
+	cmp := binLit012.Comparator()
+	assert.Equal(t, -1, cmp(binLit012, binLit013))
+	assert.Equal(t, 1, cmp(binLit013, binLit012))
+	assert.Equal(t, 0, cmp(binLit013, binLit013))
+}
+
+func TestBinaryLiteralConversions(t *testing.T) {
+	binLit012 := iceberg.NewLiteral([]byte{0x00, 0x01, 0x02})
+	fixed, err := binLit012.To(iceberg.FixedTypeOf(3))
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.FixedLiteral{0x00, 0x01, 0x02}, fixed)
+
+	_, err = binLit012.To(iceberg.FixedTypeOf(4))
+	assert.ErrorIs(t, err, iceberg.ErrBadCast)
+	assert.ErrorContains(t, err, "cannot convert BinaryLiteral to fixed[4], different length - 3 <> 4")
+
+	_, err = binLit012.To(iceberg.FixedTypeOf(2))
+	assert.ErrorIs(t, err, iceberg.ErrBadCast)
+	assert.ErrorContains(t, err, "cannot convert BinaryLiteral to fixed[2], different length - 3 <> 2")
+
+	testUuid := uuid.New()
+	lit := iceberg.NewLiteral(testUuid[:])
+	uuidLit, err := lit.To(iceberg.PrimitiveTypes.UUID)
+	require.NoError(t, err)
+	assert.EqualValues(t, testUuid, uuidLit)
+
+	_, err = binLit012.To(iceberg.PrimitiveTypes.UUID)
+	assert.ErrorIs(t, err, iceberg.ErrBadCast)
+	assert.ErrorContains(t, err, "cannot convert BinaryLiteral to UUID")
+}
+
+func testInvalidLiteralConversions(t *testing.T, lit iceberg.Literal, typs []iceberg.Type) {
+	t.Run(lit.Type().String(), func(t *testing.T) {
+		for _, tt := range typs {
+			t.Run(tt.String(), func(t *testing.T) {
+				_, err := lit.To(tt)
+				assert.ErrorIs(t, err, iceberg.ErrBadCast)
+			})
+		}
+	})
+}
+
+func TestInvalidBoolLiteralConversions(t *testing.T) {
+	testInvalidLiteralConversions(t, iceberg.NewLiteral(true), []iceberg.Type{
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Float32,
+		iceberg.PrimitiveTypes.Float64,
+		iceberg.PrimitiveTypes.Date,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.DecimalTypeOf(9, 2),
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+		iceberg.PrimitiveTypes.Binary,
+		iceberg.FixedTypeOf(2),
+	})
+}
+
+func TestInvalidNumericConversions(t *testing.T) {
+	testInvalidLiteralConversions(t, iceberg.NewLiteral(int32(34)), []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+		iceberg.FixedTypeOf(1),
+		iceberg.PrimitiveTypes.Binary,
+	})
+
+	testInvalidLiteralConversions(t, iceberg.NewLiteral(int64(34)), []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+		iceberg.FixedTypeOf(1),
+		iceberg.PrimitiveTypes.Binary,
+	})
+
+	testInvalidLiteralConversions(t, iceberg.NewLiteral(float32(34)), []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Date,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+		iceberg.FixedTypeOf(1),
+		iceberg.PrimitiveTypes.Binary,
+	})
+
+	testInvalidLiteralConversions(t, iceberg.NewLiteral(float64(34)), []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Date,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+		iceberg.FixedTypeOf(1),
+		iceberg.PrimitiveTypes.Binary,
+	})
+
+	testInvalidLiteralConversions(t, iceberg.NewLiteral(iceberg.Decimal{Val: decimal128.FromI64(3411), Scale: 2}),
+		[]iceberg.Type{
+			iceberg.PrimitiveTypes.Bool,
+			iceberg.PrimitiveTypes.Date,
+			iceberg.PrimitiveTypes.Time,
+			iceberg.PrimitiveTypes.Timestamp,
+			iceberg.PrimitiveTypes.TimestampTz,
+			iceberg.PrimitiveTypes.String,
+			iceberg.PrimitiveTypes.UUID,
+			iceberg.FixedTypeOf(1),
+			iceberg.PrimitiveTypes.Binary,
+		})
+}
+
+func TestInvalidDateTimeLiteralConversions(t *testing.T) {
+	lit, _ := iceberg.NewLiteral("2017-08-18").To(iceberg.PrimitiveTypes.Date)
+	testInvalidLiteralConversions(t, lit, []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Float32,
+		iceberg.PrimitiveTypes.Float64,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.DecimalTypeOf(9, 2),
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+		iceberg.FixedTypeOf(1),
+		iceberg.PrimitiveTypes.Binary,
+	})
+
+	lit, _ = iceberg.NewLiteral("14:21:01.919").To(iceberg.PrimitiveTypes.Time)
+	testInvalidLiteralConversions(t, lit, []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Float32,
+		iceberg.PrimitiveTypes.Float64,
+		iceberg.PrimitiveTypes.Date,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.DecimalTypeOf(9, 2),
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+		iceberg.FixedTypeOf(1),
+		iceberg.PrimitiveTypes.Binary,
+	})
+
+	lit, _ = iceberg.NewLiteral("2017-08-18T14:21:01.919").To(iceberg.PrimitiveTypes.Timestamp)
+	testInvalidLiteralConversions(t, lit, []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Float32,
+		iceberg.PrimitiveTypes.Float64,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.DecimalTypeOf(9, 2),
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+		iceberg.FixedTypeOf(1),
+		iceberg.PrimitiveTypes.Binary,
+	})
+}
+
+func TestInvalidStringLiteralConversions(t *testing.T) {
+	testInvalidLiteralConversions(t, iceberg.NewLiteral("abc"), []iceberg.Type{
+		iceberg.FixedTypeOf(1), iceberg.PrimitiveTypes.Binary,
+	})
+}
+
+func TestInvalidBinaryLiteralConversions(t *testing.T) {
+	testInvalidLiteralConversions(t, iceberg.NewLiteral(uuid.New()), []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Float32,
+		iceberg.PrimitiveTypes.Float64,
+		iceberg.PrimitiveTypes.Date,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.DecimalTypeOf(9, 2),
+		iceberg.PrimitiveTypes.String,
+		iceberg.FixedTypeOf(1),
+	})
+
+	lit, _ := iceberg.NewLiteral([]byte{0x00, 0x01, 0x02}).To(iceberg.FixedTypeOf(3))
+	testInvalidLiteralConversions(t, lit, []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Float32,
+		iceberg.PrimitiveTypes.Float64,
+		iceberg.PrimitiveTypes.Date,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.DecimalTypeOf(9, 2),
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+	})
+
+	testInvalidLiteralConversions(t, iceberg.NewLiteral([]byte{0x00, 0x01, 0x02}), []iceberg.Type{
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Float32,
+		iceberg.PrimitiveTypes.Float64,
+		iceberg.PrimitiveTypes.Date,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.DecimalTypeOf(9, 2),
+		iceberg.PrimitiveTypes.String,
+		iceberg.PrimitiveTypes.UUID,
+	})
+}
+
+func TestBadStringLiteralCasts(t *testing.T) {
+	tests := []iceberg.Type{
+		iceberg.PrimitiveTypes.Int32,
+		iceberg.PrimitiveTypes.Int64,
+		iceberg.PrimitiveTypes.Float32,
+		iceberg.PrimitiveTypes.Float64,
+		iceberg.PrimitiveTypes.Date,
+		iceberg.PrimitiveTypes.Time,
+		iceberg.PrimitiveTypes.Timestamp,
+		iceberg.PrimitiveTypes.TimestampTz,
+		iceberg.PrimitiveTypes.Bool,
+		iceberg.DecimalTypeOf(9, 2),
+		iceberg.PrimitiveTypes.UUID,
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.String(), func(t *testing.T) {
+			_, err := iceberg.NewLiteral("abc").To(tt)
+			assert.ErrorIs(t, err, iceberg.ErrBadCast)
+		})
+	}
+}
+
+func TestStringLiteralToIntMaxMinValue(t *testing.T) {
+	above, err := iceberg.NewLiteral(strconv.FormatInt(math.MaxInt32+1, 10)).
+		To(iceberg.PrimitiveTypes.Int32)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Int32AboveMaxLiteral(), above)
+
+	below, err := iceberg.NewLiteral(strconv.FormatInt(math.MinInt32-1, 10)).
+		To(iceberg.PrimitiveTypes.Int32)
+	require.NoError(t, err)
+	assert.Equal(t, iceberg.Int32BelowMinLiteral(), below)
+}
diff --git a/types.go b/types.go
index 8ca0ad7..5aabdb4 100644
--- a/types.go
+++ b/types.go
@@ -23,7 +23,9 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"time"
 
+	"github.com/apache/arrow/go/v16/arrow/decimal128"
 	"golang.org/x/exp/slices"
 )
 
@@ -407,6 +409,7 @@ func (f FixedType) Equals(other Type) bool {
 func (f FixedType) Len() int       { return f.len }
 func (f FixedType) Type() string   { return fmt.Sprintf("fixed[%d]", f.len) }
 func (f FixedType) String() string { return fmt.Sprintf("fixed[%d]", f.len) }
+func (f FixedType) primitive()     {}
 
 func DecimalTypeOf(prec, scale int) DecimalType {
 	return DecimalType{precision: prec, scale: scale}
@@ -430,6 +433,12 @@ func (d DecimalType) Type() string   { return fmt.Sprintf("decimal(%d, %d)", d.p
 func (d DecimalType) String() string { return fmt.Sprintf("decimal(%d, %d)", d.precision, d.scale) }
 func (d DecimalType) Precision() int { return d.precision }
 func (d DecimalType) Scale() int     { return d.scale }
+func (DecimalType) primitive()       {}
+
+type Decimal struct {
+	Val   decimal128.Num
+	Scale int
+}
 
 type PrimitiveType interface {
 	Type
@@ -527,6 +536,11 @@ func (TimeType) String() string { return "time" }
 
 type Timestamp int64
 
+func (t Timestamp) ToDate() Date {
+	tm := time.UnixMicro(int64(t)).UTC()
+	return Date(tm.Truncate(24*time.Hour).Unix() / int64((time.Hour * 24).Seconds()))
+}
+
 // TimestampType represents a number of microseconds since the unix epoch
 // without regard for timezone.
 type TimestampType struct{}
diff --git a/utils.go b/utils.go
index 907a35f..fd669f8 100644
--- a/utils.go
+++ b/utils.go
@@ -18,10 +18,9 @@
 package iceberg
 
 import (
+	"cmp"
 	"runtime/debug"
 	"strings"
-
-	"golang.org/x/exp/constraints"
 )
 
 var version string
@@ -40,7 +39,7 @@ func init() {
 
 func Version() string { return version }
 
-func max[T constraints.Ordered](vals ...T) T {
+func max[T cmp.Ordered](vals ...T) T {
 	if len(vals) == 0 {
 		panic("can't call max with no arguments")
 	}