diff --git a/Changes b/Changes
index a25af19..92f33ee 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,8 @@
 Revision history for DBIx::Class
 
+    * New Features / Changes
+        - Allow tables to be marked readonly
+
 0.08195 2011-07-27 16:20 (UTC)
     * Fixes
         - Fix horrible oversight in the Oracle sqlmaker when dealing with
diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm
index aaff394..fc8b814 100644
--- a/lib/DBIx/Class/ResultSet.pm
+++ b/lib/DBIx/Class/ResultSet.pm
@@ -1699,6 +1699,8 @@ sub _rs_update_delete {
   my ($self, $op, $values) = @_;
 
   my $rsrc = $self->result_source;
+  $self->throw_exception("Can't modify a readonly table")
+    if $rsrc->readonly;
 
   my $needs_group_by_subq = $self->_has_resolved_attr (qw/collapse group_by -join/);
   my $needs_subq = $needs_group_by_subq || $self->_has_resolved_attr(qw/rows offset/);
diff --git a/lib/DBIx/Class/ResultSource.pm b/lib/DBIx/Class/ResultSource.pm
index 9489f49..1e81e57 100644
--- a/lib/DBIx/Class/ResultSource.pm
+++ b/lib/DBIx/Class/ResultSource.pm
@@ -16,7 +16,7 @@ use namespace::clean;
 use base qw/DBIx::Class/;
 
 __PACKAGE__->mk_group_accessors(simple => qw/
-  source_name name source_info
+  source_name name source_info readonly
   _ordered_columns _columns _primaries _unique_constraints
   _relationships resultset_attributes
   column_info_from_storage
@@ -1982,6 +1982,11 @@ and don't actually accomplish anything on their own:
     "_engine" => 'InnoDB',
   });
 
+=head2 readonly
+
+A flag for whether this source should be treated as readonly.  If it is then
+you can read from it, but various write operations will fail.
+
 =head2 new
 
   $class->new();
diff --git a/lib/DBIx/Class/ResultSourceProxy.pm b/lib/DBIx/Class/ResultSourceProxy.pm
index 1f74eea..c779d75 100644
--- a/lib/DBIx/Class/ResultSourceProxy.pm
+++ b/lib/DBIx/Class/ResultSourceProxy.pm
@@ -80,6 +80,8 @@ for my $method_to_proxy (qw/
   relationships
   relationship_info
   has_relationship
+
+  readonly
 /) {
   no strict qw/refs/;
   *{__PACKAGE__."::$method_to_proxy"} = subname $method_to_proxy => sub {
diff --git a/lib/DBIx/Class/Row.pm b/lib/DBIx/Class/Row.pm
index 4eaa431..57e118b 100644
--- a/lib/DBIx/Class/Row.pm
+++ b/lib/DBIx/Class/Row.pm
@@ -270,6 +270,7 @@ sub new {
 Inserts an object previously created by L</new> into the database if
 it isn't already in there. Returns the object itself. To insert an
 entirely new row into the database, use L<DBIx::Class::ResultSet/create>.
+Attempting to use this on a readonly table will throw an exception.
 
 To fetch an uninserted row object, call
 L<new|DBIx::Class::ResultSet/new> on a resultset.
@@ -282,11 +283,13 @@ one, see L<DBIx::Class::ResultSet/create> for more details.
 sub insert {
   my ($self) = @_;
   return $self if $self->in_storage;
-  my $source = $self->result_source;
+  my $rsrc = $self->result_source;
   $self->throw_exception("No result_source set on this object; can't insert")
-    unless $source;
+    unless $rsrc;
+  $self->throw_exception("Can't insert into a readonly table")
+    if $rsrc->readonly;
 
-  my $storage = $source->storage;
+  my $storage = $rsrc->storage;
 
   my $rollback_guard;
 
@@ -302,7 +305,7 @@ sub insert {
     if (! $self->{_rel_in_storage}{$relname}) {
       next unless (blessed $rel_obj && $rel_obj->isa('DBIx::Class::Row'));
 
-      next unless $source->_pk_depends_on(
+      next unless $rsrc->_pk_depends_on(
                     $relname, { $rel_obj->get_columns }
                   );
 
@@ -348,7 +351,7 @@ sub insert {
   # (autoinc primary columns and any retrieve_on_insert columns)
   my %current_rowdata = $self->get_columns;
   my $returned_cols = $storage->insert(
-    $source,
+    $rsrc,
     { %current_rowdata }, # what to insert, copy because the storage *will* change it
   );
 
@@ -372,7 +375,7 @@ sub insert {
   $self->{related_resultsets} = {};
 
   foreach my $relname (keys %related_stuff) {
-    next unless $source->has_relationship ($relname);
+    next unless $rsrc->has_relationship ($relname);
 
     my @cands = ref $related_stuff{$relname} eq 'ARRAY'
       ? @{$related_stuff{$relname}}
@@ -381,7 +384,7 @@ sub insert {
 
     if (@cands && blessed $cands[0] && $cands[0]->isa('DBIx::Class::Row')
     ) {
-      my $reverse = $source->reverse_relationship_info($relname);
+      my $reverse = $rsrc->reverse_relationship_info($relname);
       foreach my $obj (@cands) {
         $obj->set_from_related($_, $self) for keys %$reverse;
         if ($self->__their_pk_needs_us($relname)) {
@@ -449,7 +452,8 @@ sub in_storage {
 =back
 
 Throws an exception if the row object is not yet in the database,
-according to L</in_storage>.
+according to L</in_storage>.  Also throws an exception if the table
+has been marked readonly.
 
 This method issues an SQL UPDATE query to commit any changes to the
 object to the database if required (see L</get_dirty_columns>).
@@ -500,8 +504,12 @@ sub update {
 
   $self->throw_exception( "Not in database" ) unless $self->in_storage;
 
-  my $rows = $self->result_source->storage->update(
-    $self->result_source, \%to_update, $self->_storage_ident_condition
+  my $rsrc = $self->result_source;
+  $self->throw_exception("Can't update a readonly row")
+    if $rsrc->readonly;
+
+  my $rows = $rsrc->storage->update(
+    $rsrc, \%to_update, $self->_storage_ident_condition
   );
   if ($rows == 0) {
     $self->throw_exception( "Can't update ${self}: row not found" );
@@ -530,7 +538,8 @@ Throws an exception if the object is not in the database according to
 L</in_storage>. Also throws an exception if a proper WHERE clause
 uniquely identifying the database row can not be constructed (see
 L<significance of primary keys|DBIx::Class::Manual::Intro/The Significance and Importance of Primary Keys>
-for more details).
+for more details).  Also throws an exception if called on a readonly
+table.
 
 The object is still perfectly usable, but L</in_storage> will
 now return 0 and the object must be reinserted using L</insert>
@@ -562,9 +571,10 @@ sub delete {
   if (ref $self) {
     $self->throw_exception( "Not in database" ) unless $self->in_storage;
 
-    $self->result_source->storage->delete(
-      $self->result_source, $self->_storage_ident_condition
-    );
+    my $rsrc = $self->result_source;
+    $self->throw_exception("Can't delete from a readonly table")
+      if $rsrc->readonly;
+    $rsrc->storage->delete($rsrc, $self->_storage_ident_condition);
 
     delete $self->{_column_data_in_storage};
     $self->in_storage(undef);
@@ -573,6 +583,9 @@ sub delete {
     my $rsrc = try { $self->result_source_instance }
       or $self->throw_exception("Can't do class delete without a ResultSource instance");
 
+    $self->throw_exception("Can't delete from a readonly class")
+      if $rsrc->readonly;
+
     my $attrs = @_ > 1 && ref $_[$#_] eq 'HASH' ? { %{pop(@_)} } : {};
     my $query = ref $_[0] eq 'HASH' ? $_[0] : {@_};
     $rsrc->resultset->search(@_)->delete;
