[svn:dbd-oracle] r12438 - in dbd-oracle/trunk: . t

[email protected]
Newsgroups perl.dbd.oracle.changes
Message-ID <[email protected]>
Author: byterock
Date: Wed Jan 14 07:28:59 2009
New Revision: 12438

Added:
   dbd-oracle/trunk/Object.pm
   dbd-oracle/trunk/t/58object.t
Modified:
   dbd-oracle/trunk/Changes
   dbd-oracle/trunk/Oracle.pm
   dbd-oracle/trunk/dbdimp.c
   dbd-oracle/trunk/dbdimp.h
   dbd-oracle/trunk/oci8.c
   dbd-oracle/trunk/ocitrace.h

Log:
 Added rt.cpan.org Ticket #=42328 ora_objects attribute for extended embedded objects support from tnt at netsafe.cz
  Fix for rt.cpan.org Ticket #=42328 user defined types from different schema in describe_obj from tnt at netsafe.cz
  

Modified: dbd-oracle/trunk/Changes
==============================================================================
--- dbd-oracle/trunk/Changes	(original)
+++ dbd-oracle/trunk/Changes	Wed Jan 14 07:28:59 2009
@@ -1,4 +1,6 @@
 =head1 Changes in DBD-Oracle 1.23(svn rev #####)
+  Added rt.cpan.org Ticket #=42328 ora_objects attribute for extended embedded objects support from tnt at netsafe.cz
+  Fix for rt.cpan.org Ticket #=42328 user defined types from different schema in describe_obj from tnt at netsafe.cz
   Added a README for sun suggested by Jim McCullars
   Clean up of white space and formating to 4 tabs  from John Scoles
   Fix for GCC 4.3 warnings from Eric Simon

Added: dbd-oracle/trunk/Object.pm
==============================================================================
--- (empty file)
+++ dbd-oracle/trunk/Object.pm	Wed Jan 14 07:28:59 2009
@@ -0,0 +1,24 @@
+package DBD::Oracle::Object;
+
+use strict;
+use warnings;
+
+sub type_name {  shift->{type_name}  }
+
+sub attributes {  @{shift->{attributes}}  }
+
+sub attr_hash {
+	my $self = shift;
+	return $self->{attr_hash} ||= { $self->attributes };
+}
+
+sub attr {
+	my $self = shift;
+	if (@_) {
+		my $key = shift;
+		return $self->attr_hash->{$key};
+	}
+	return $self->attr_hash;
+}
+
+1;
\ No newline at end of file

Modified: dbd-oracle/trunk/Oracle.pm
==============================================================================
--- dbd-oracle/trunk/Oracle.pm	(original)
+++ dbd-oracle/trunk/Oracle.pm	Wed Jan 14 07:28:59 2009
@@ -17,6 +17,8 @@
     use DBI ();
     use DynaLoader ();
     use Exporter ();
+    use Object;
+
     @ISA = qw(DynaLoader Exporter);
     %EXPORT_TAGS = (
       ora_types => [ qw(
@@ -319,7 +321,8 @@
                  ora_ncharset		=> undef,
                  ora_session_mode	=> undef,
                  ora_verbose		=> undef,
-                 ora_oci_success_warn	=> undef
+                 ora_oci_success_warn	=> undef,
+                 ora_objects	=> undef
                  };
     }
    
@@ -1519,6 +1522,24 @@
 
   $dbh->{ora_oci_success_warn} =1;
 
+=item ora_objects 
+
+Use this value to enable extended embedded oracle objects mode. In extended:
+
+=over 8
+
+=item 1
+
+Embedded objects are returned as <DBD::Oracle::Object> instance (including type-name etc.) instead of simple ARRAY. 
+
+=item 2
+
+Determine object type for each instance. All object attributes are returned (not only super-type's attributes). 
+
+=back 
+
+  $dbh->{ora_objects} = 1;
+
 =item ora_ph_type
 
 The default placeholder data type for the database session.

Modified: dbd-oracle/trunk/dbdimp.c
==============================================================================
--- dbd-oracle/trunk/dbdimp.c	(original)
+++ dbd-oracle/trunk/dbdimp.c	Wed Jan 14 07:28:59 2009
@@ -43,6 +43,8 @@
 int is_extproc	= 0;
 int dbd_verbose	= 0; /* DBD only debugging*/
 int oci_warn	= 0; /* show oci warnings */
+int ora_objects	= 0; /* get oracle embedded objects as instance of DBD::Oracle::Object */
+
 /* bitflag constants for figuring out how to handle utf8 for array binds */
 #define ARRAY_BIND_NATIVE 0x01
 #define ARRAY_BIND_UTF8   0x02
@@ -103,7 +105,7 @@
 		if (*(SvEND(errstr)-1) == '\n')
 			--SvCUR(errstr);
 	}
-	
+
 	if (what || status != OCI_ERROR) {
 		sv_catpv(errstr, (debug<0) ? " (" : " (DBD ");
 		sv_catpv(errstr, oci_status_name(status));
@@ -379,7 +381,8 @@
 		DBD_ATTRIB_GET_IV(  attr, "ora_verbose",  11, svp, dbd_verbose);
 	if (DBD_ATTRIB_TRUE(attr,"ora_oci_success_warn",20,svp))
 		DBD_ATTRIB_GET_IV(  attr, "ora_oci_success_warn",  20, svp, oci_warn);
-
+	if (DBD_ATTRIB_TRUE(attr,"ora_objects",11,svp))
+		DBD_ATTRIB_GET_IV(  attr, "ora_objects",11, svp, ora_objects);
 
 	/* dbi_imp_data code adapted from DBD::mysql */
 	if (DBIc_has(imp_dbh, DBIcf_IMPSET)) {
@@ -954,6 +957,9 @@
 	if (kl==20 && strEQ(key, "ora_oci_success_warn") ) {
 		oci_warn = SvIV (valuesv);
 	}
+	else if (kl==11 && strEQ(key, "ora_objects")) {
+		ora_objects = SvIV (valuesv);
+	}
 	else if (kl==11 && (strEQ(key, "ora_verbose") || strEQ(key, "dbd_verbose"))) {
 		dbd_verbose = SvIV (valuesv);
 	}
@@ -989,7 +995,7 @@
 
 	if (cacheit) /* cache value for later DBI 'quick' fetch? */
 		(void)hv_store((HV*)SvRV(dbh), key, kl, newSVsv(valuesv), 0);
-		
+
 	return TRUE;
 }
 
@@ -1009,6 +1015,9 @@
 	if (kl==20 && strEQ(key, "ora_oci_success_warn")) {
 		retsv = newSViv (oci_warn);
 	}
+	else if (kl==11 && strEQ(key, "ora_objects")) {
+		retsv = newSViv (ora_objects);
+	}
 	else if (kl==11 && (strEQ(key, "ora_verbose") || strEQ(key, "dbd_verbose"))) {
 		retsv = newSViv (dbd_verbose);
 	}
@@ -1230,28 +1239,28 @@
 			dest = start+strlen(start);
 			style = "?";
 
-		} 
+		}
 		else if (isDIGIT(*src)) {	/* ':1'		*/
 			idx = atoi(src);
 			*dest++ = 'p';		/* ':1'->':p1'	*/
 			if (idx <= 0)
 				croak("Placeholder :%d invalid, placeholders must be >= 1", idx);
-		
+
 			while(isDIGIT(*src))
 				*dest++ = *src++;
 			style = ":1";
 
-		} 
+		}
 		else if (isALNUM(*src)) {	/* ':foo'	*/
 			while(isALNUM(*src))	/* includes '_'	*/
 				*dest++ = toLOWER(*src), src++;
 			style = ":foo";
-		
+
 		} else {			/* perhaps ':=' PL/SQL construct */
 			/* if (src == ':') *dest++ = *src++; XXX? move past '::'? */
 			continue;
 		}
-		
+
 		*dest = '\0';			/* handy for debugging	*/
 		namelen = (dest-start);
 		if (laststyle && style != laststyle)
@@ -2450,7 +2459,7 @@
 	}
 
 	return 1;
-	
+
 }
 
 static int
@@ -3849,7 +3858,7 @@
 		while(--i >= 0)
 			av_store(av, i, newSVpv((char*)imp_sth->fbh[i].name,0));
 
-	} 
+	}
 	else if (kl==11 && strEQ(key, "ParamValues")) {
 		HV *pvhv = newHV();
 		if (imp_sth->all_params_hv) {
@@ -3865,55 +3874,55 @@
 		retsv = newRV_noinc((SV*)pvhv);
 		cacheit = FALSE;
 
-	} 
+	}
 	else if (kl==11 && strEQ(key, "ora_lengths")) {
 		AV *av = newAV();
 		retsv = newRV(sv_2mortal((SV*)av));
 		while(--i >= 0)
 			av_store(av, i, newSViv((IV)imp_sth->fbh[i].disize));
-	} 
+	}
 	else if (kl==9 && strEQ(key, "ora_types")) {
 		AV *av = newAV();
 		retsv = newRV(sv_2mortal((SV*)av));
 		while(--i >= 0)
 			av_store(av, i, newSViv(imp_sth->fbh[i].dbtype));
-	} 
+	}
 	else if (kl==4 && strEQ(key, "TYPE")) {
 		AV *av = newAV();
 		retsv = newRV(sv_2mortal((SV*)av));
 		while(--i >= 0)
 			av_store(av, i, newSViv(ora2sql_type(imp_sth->fbh+i).dbtype));
-	} 
+	}
 	else if (kl==5 && strEQ(key, "SCALE")) {
 		AV *av = newAV();
 		retsv = newRV(sv_2mortal((SV*)av));
 		while(--i >= 0)
 			av_store(av, i, newSViv(ora2sql_type(imp_sth->fbh+i).scale));
-	} 
+	}
 	else if (kl==9 && strEQ(key, "PRECISION")) {
 		AV *av = newAV();
 		retsv = newRV(sv_2mortal((SV*)av));
 		while(--i >= 0)
 			av_store(av, i, newSViv(ora2sql_type(imp_sth->fbh+i).prec));
 #ifdef XXX
-	} 
+	}
 	else if (kl==9 && strEQ(key, "ora_rowid")) {
 		/* return current _binary_ ROWID (oratype 11) uncached	*/
 		/* Use { ora_type => 11 } when binding to a placeholder	*/
 		retsv = newSVpv((char*)&imp_sth->cda->rid, sizeof(imp_sth->cda->rid));
 		cacheit = FALSE;
 #endif
-	} 
+	}
 	else if (kl==17 && strEQ(key, "ora_est_row_width")) {
 		retsv = newSViv(imp_sth->est_width);
 		cacheit = TRUE;
-	} 
+	}
 	else if (kl==8 && strEQ(key, "NULLABLE")) {
 		AV *av = newAV();
 		retsv = newRV(sv_2mortal((SV*)av));
 		while(--i >= 0)
 			av_store(av, i, boolSV(imp_sth->fbh[i].nullok));
-	} 
+	}
 	else {
 		return Nullsv;
 	}

Modified: dbd-oracle/trunk/dbdimp.h
==============================================================================
--- dbd-oracle/trunk/dbdimp.h	(original)
+++ dbd-oracle/trunk/dbdimp.h	Wed Jan 14 07:28:59 2009
@@ -152,7 +152,10 @@
  	OCIType	  		*obj_type;		 	/*if an embeded object this is the  OCIType returned by a OCIObjectPin*/
 	fbh_obj_t		*fields;			/*one object for each field/property*/
 	int				field_count;		/*The number of fields Not really needed but nice to have*/
+	fbh_obj_t		*next_subtype;		/*There is strored information about subtypes for inteherited objects*/
 	AV				*value;				/*The value to send back to Perl This way there are no memory leaks*/
+	SV				*full_type_name;	/*Perl value of full type name = schema_name "." type_name*/
+
 };
 
 struct imp_fbh_st { 	/* field buffer EXPERIMENTAL */
@@ -251,6 +254,7 @@
 extern int ora_fetchtest;
 extern int dbd_verbose;
 extern int oci_warn;
+extern int ora_objects;
 
 extern ub2 charsetid;
 extern ub2 ncharsetid;

Modified: dbd-oracle/trunk/oci8.c
==============================================================================
--- dbd-oracle/trunk/oci8.c	(original)
+++ dbd-oracle/trunk/oci8.c	Wed Jan 14 07:28:59 2009
@@ -19,6 +19,9 @@
 
 DBISTATE_DECLARE;
 
+int describe_obj_by_tdo(SV *sth,imp_sth_t *imp_sth,fbh_obj_t *obj,int level );
+int dump_struct(imp_sth_t *imp_sth,fbh_obj_t *obj,int level);
+
 
 
 void
@@ -567,11 +570,10 @@
 		DBD_ATTRIB_GET_IV(  attribs, "ora_prefetch_memory",  19, svp, imp_sth->prefetch_memory);
 		DBD_ATTRIB_GET_IV(  attribs, "ora_verbose",  11, svp, dbd_verbose);
 		DBD_ATTRIB_GET_IV(  attribs, "ora_oci_success_warn",  20, svp, oci_warn);
+		DBD_ATTRIB_GET_IV(  attribs, "ora_objects",  11, svp, ora_objects);
 
-  			if (!dbd_verbose)
-				DBD_ATTRIB_GET_IV(  attribs, "dbd_verbose",  11, svp, dbd_verbose);
-
-
+		if (!dbd_verbose)
+			DBD_ATTRIB_GET_IV(  attribs, "dbd_verbose",  11, svp, dbd_verbose);
 		}
 
 
@@ -656,7 +658,7 @@
 	AV *av;
 	SV **sv_p;
 	if( bindp ){ /* For GCC not to warn on unused parameter*/ }
-	
+
 	tuples_av = phs->imp_sth->bind_tuples;
 	if(tuples_av) {
 		/* NOTE: we already checked the validity in ora_st_bind_for_array_exec(). */
@@ -668,7 +670,7 @@
 			*bufpp = SvPV(sv, phs_len);
 			phs->alen = (phs->alen_incnull) ? phs_len+1 : phs_len;
 			phs->indp = 0;
-		} 
+		}
 		else {
 			*bufpp = SvPVX(sv);
 			phs->alen = 0;
@@ -765,7 +767,7 @@
 		*bufpp  = phs->desc_h;
 		phs->alen = 0;
 
-	} 
+	}
 	else {
 		SV *sv = phs->sv;
 
@@ -1007,15 +1009,15 @@
 		imp_sth_nested->errhp = imp_sth->errhp;
 		imp_sth_nested->srvhp = imp_sth->srvhp;
 		imp_sth_nested->svchp = imp_sth->svchp;
-	
+
 		imp_sth_nested->stmhp = stmhp_nested;
 		imp_sth_nested->nested_cursor = 1;
 		imp_sth_nested->rs_array_on = 1;
 		imp_sth_nested->stmt_type = OCI_STMT_SELECT;
-	
+
 		DBIc_IMPSET_on(imp_sth_nested);
 		DBIc_ACTIVE_on(imp_sth_nested);  /* So describe won't do an execute */
-	
+
 		if (!dbd_describe(dest_sv, imp_sth_nested))
 			return 0;
 	}
@@ -1090,7 +1092,7 @@
 		if (SvOK(phs->sv)) {	/* ie a number, convert to string ASAP  */
 			if (!(SvROK(phs->sv) && phs->is_inout))
 				sv_2pv(phs->sv, &na);
-		} 
+		}
 		else { /* ensure we're at least an SVt_PV (so SvPVX etc work)	 */
 			if(SvUPGRADE(phs->sv, SVt_PV)){} /* For GCC not to warn on unused result */
 		}
@@ -1122,7 +1124,7 @@
 		ub4 amtp;
 
 		if(SvUPGRADE(phs->sv, SVt_PV)){/* For GCC not to warn on unused result */};	/* just in case */
-		
+
 		amtp = SvCUR(phs->sv);		/* XXX UTF8? */
 
 		/* Create a temp lob for non-empty string */
@@ -1754,10 +1756,27 @@
 	}
 }
 
+
+SV* new_ora_object (AV* list, OCITypeCode typecode) {
+	dTHX;
+	SV* objref = newRV_noinc((SV*) list);
+
+	if (ora_objects && typecode == OCI_TYPECODE_OBJECT) {
+		HV* self = newHV();
+		(void)hv_store(self, "type_name", 9, av_shift(list), 0);
+		(void)hv_store(self, "attributes", 10, objref, 0);
+		objref = newRV_noinc((SV*) self);
+		objref = sv_bless(objref, gv_stashpv("DBD::Oracle::Object", 0));
+
+	}
+	return objref;
+}
+
 /*gets the properties of an object from a fetch by using the attributes saved in the describe */
 
 int
-get_object (SV *sth, AV *list, imp_fbh_t *fbh,fbh_obj_t *obj,OCIComplexObject *value){
+get_object (SV *sth, AV *list, imp_fbh_t *fbh,fbh_obj_t *base_obj,OCIComplexObject *value){
+
 	dTHX;
 	sword 		status;
 	dvoid		*element ;
@@ -1771,6 +1790,7 @@
 	OCIIter  	*itr;
 	fbh_obj_t	*fld;
 	OCIInd		*obj_ind;
+	fbh_obj_t	*obj = base_obj;
 
 	if (DBIS->debug >= 5 || dbd_verbose >= 5 ) {
 		PerlIO_printf(DBILOGFP, " getting attributes of object named  %s with typecode=%s\n",obj->type_name,oci_typecode_name(obj->typecode));
@@ -1780,12 +1800,72 @@
 
 		case OCI_TYPECODE_OBJECT :							/* embedded ADT */
 
+			if (ora_objects){
+
+				OCIRef  *type_ref=0;
+				sword   status;
+				OCIType *tdo;
+
+				status = OCIObjectNew(fbh->imp_sth->envhp, fbh->imp_sth->errhp, fbh->imp_sth->svchp,
+													OCI_TYPECODE_REF, (OCIType *)0,
+													(dvoid *)0, OCI_DURATION_DEFAULT, TRUE,
+													(dvoid **) &type_ref);
+				if (status != OCI_SUCCESS) {
+					oci_error(sth, fbh->imp_sth->errhp, status, "OCIObjectNew");
+					return 0;
+				}
+
+				status=OCIObjectGetTypeRef(fbh->imp_sth->envhp,fbh->imp_sth->errhp, (dvoid*)fbh->obj->obj_value, type_ref);
+				if (status != OCI_SUCCESS) {
+					oci_error(sth, fbh->imp_sth->errhp, status, "OCIObjectGetTypeRef");
+					return 0;
+				}
+
+				OCITypeByRef_log_stat(fbh->imp_sth->envhp,fbh->imp_sth->errhp,type_ref,&tdo,status);
+
+				if (status != OCI_SUCCESS) {
+					oci_error(sth, fbh->imp_sth->errhp, status, "OCITypeByRef");
+					return 0;
+				}
+
+				if (tdo != obj->tdo) {
+					/* this is subtype -> search for subtype obj */
+					while (obj->next_subtype && tdo != obj->tdo) {
+						obj = obj->next_subtype;
+					}
+					if (tdo != obj->tdo) {
+						/* new subtyped -> get obj description */
+						if (DBIS->debug >= 5 || dbd_verbose >= 5 ) {
+							PerlIO_printf(DBILOGFP, " describe subtype of object type %s\n",base_obj->type_name);
+						}
+
+						Newz(1, obj->next_subtype, 1, fbh_obj_t);
+						obj->next_subtype->tdo = tdo;
+						if ( describe_obj_by_tdo(sth, fbh->imp_sth, obj->next_subtype, 0 /*unknown level there*/) ) {
+							obj = obj->next_subtype;
+							if (DBIS->debug >= 5 || dbd_verbose >= 5 ){
+								dump_struct(fbh->imp_sth,obj,0);
+							}
+						}
+						else {
+							obj->next_subtype = 0;
+						}
+					}
+
+					if (DBIS->debug >= 5 || dbd_verbose >= 5 ) {
+						PerlIO_printf(DBILOGFP, " getting attributes of object subtype  %s\n",obj->type_name);
+					}
+				}
+
+				av_push(list, newSVpv((char*)obj->type_name, obj->type_namel));
+			}
+
 			if (obj->obj_ind) {
 				obj_ind = obj->obj_ind;
 			} else {
 
 				status=OCIObjectGetInd(fbh->imp_sth->envhp,fbh->imp_sth->errhp,value,(dvoid**)&obj_ind);
-	
+
 				if (status != OCI_SUCCESS) {
 					oci_error(sth, fbh->imp_sth->errhp, status, "OCIObjectGetInd");
 					return 0;
@@ -1793,7 +1873,13 @@
 			}
 
 			for (pos = 0; pos < obj->field_count; pos++){
-  	  			fld = &obj->fields[pos]; /*get the field */
+
+				fld = &obj->fields[pos]; /*get the field */
+
+				if (ora_objects) {
+					/* add field name */
+					av_push(list, newSVpv((char*)fld->type_name, fld->type_namel));
+				}
 
 				status=OCIObjectGetInd(fbh->imp_sth->envhp,fbh->imp_sth->errhp,value,(dvoid**)&obj->obj_ind);
 /*
@@ -1837,8 +1923,9 @@
 						fld->fields[0].value = newAV();
 						if (fld->typecode != OCI_TYPECODE_OBJECT)
 							attr_value = *(dvoid **)attr_value;
+
 						get_object (sth,fld->fields[0].value, fbh, &fld->fields[0],attr_value);
-						av_push(list, newRV_noinc((SV *) fld->fields[0].value));
+						av_push(list, new_ora_object(fld->fields[0].value, fld->typecode));
 
 					} else{  /* else, display the scaler type attribute */
 
@@ -1872,14 +1959,14 @@
 						(dvoid **) &element,
 						(dvoid **) &element_null, &eoc) && !eoc;)
 					{
-	
+
 						if (*element_null==OCI_IND_NULL){
 							av_push(list,  &sv_undef);
 						} else {
 							if (obj->element_typecode == OCI_TYPECODE_OBJECT || obj->element_typecode == OCI_TYPECODE_VARRAY || obj->element_typecode== OCI_TYPECODE_TABLE || obj->element_typecode== OCI_TYPECODE_NAMEDCOLLECTION){
 								fld->value = newAV();
-				 				get_object (sth,fld->value, fbh, fld,element);
-								av_push(list, newRV_noinc((SV *) fld->value));
+								get_object (sth,fld->value, fbh, fld,element);
+								av_push(list, new_ora_object(fld->value, obj->element_typecode));
 							} else{  /* else, display the scaler type attribute */
 								get_attr_val(sth,list, fbh, obj->type_name, obj->element_typecode, element);
 							}
@@ -1932,7 +2019,7 @@
   	if (!get_object(sth,fbh->obj->value,fbh,fbh->obj,fbh->obj->obj_value)){
   		return 0;
 	} else {
-		sv_setsv(dest_sv, sv_2mortal(newRV_noinc((SV *) fbh->obj->value)));
+		sv_setsv(dest_sv, sv_2mortal(new_ora_object(fbh->obj->value, fbh->obj->typecode)));
 		return 1;
 	}
 
@@ -2080,18 +2167,22 @@
 
 			case OCI_TYPECODE_OBJECT :		/* embedded ADT */
 
+				if (obj->next_subtype) {
+					empty_oci_object(obj->next_subtype);
+				}
+
 				for (pos = 0; pos < obj->field_count; pos++){
-				fld = &obj->fields[pos]; /*get the field */
-				if (fld->typecode == OCI_TYPECODE_OBJECT || fld->typecode == OCI_TYPECODE_VARRAY || fld->typecode == OCI_TYPECODE_TABLE || fld->typecode == OCI_TYPECODE_NAMEDCOLLECTION){
-					empty_oci_object(fld);
-					if (fld->value && SvTYPE(fld->value) == SVt_PVAV){
-						av_clear(fld->value);
-			 			av_undef(fld->value);
-					}
+					fld = &obj->fields[pos]; /*get the field */
+					if (fld->typecode == OCI_TYPECODE_OBJECT || fld->typecode == OCI_TYPECODE_VARRAY || fld->typecode == OCI_TYPECODE_TABLE || fld->typecode == OCI_TYPECODE_NAMEDCOLLECTION){
+						empty_oci_object(fld);
+						if (fld->value && SvTYPE(fld->value) == SVt_PVAV){
+							av_clear(fld->value);
+				 			av_undef(fld->value);
+						}
 
-				} else {
-					return 1;
-				}
+					} else {
+						return 1;
+					}
 				}
 			break;
 
@@ -2160,10 +2251,10 @@
 	if (imp_sth->rs_array_on!=1		||
 		imp_sth->rs_array_size<1	||
 		imp_sth->rs_array_size>128){
-		
+
 		imp_sth->rs_array_on=0;
 		imp_sth->rs_array_size=1;
-		
+
 	}
 	imp_sth->rs_array_num_rows=0;
 	imp_sth->rs_array_idx=0;
@@ -2194,7 +2285,7 @@
 	/* number of rows to cache	 if using oraperl */
 	if (SvOK(imp_drh->ora_cache_o)){
 		imp_sth->cache_rows = SvIV(imp_drh->ora_cache_o);
-	} 
+	}
 	else if (SvOK(imp_drh->ora_cache)){
 		imp_sth->cache_rows = SvIV(imp_drh->ora_cache);
 	}
@@ -2271,41 +2362,49 @@
 {
 	dTHX;
 	sword status;
+	OCIRef *type_ref;
 
 	if (DBIS->debug >= 5 || dbd_verbose >= 5 ) {
 		PerlIO_printf(DBILOGFP, "At level=%d in description an embedded object \n",level);
 	}
 	/*Describe the field (OCIParm) we know it is a object or a collection */
 
-	OCIAttrGet_parmdp(imp_sth,parm, &obj->type_name, &obj->type_namel, OCI_ATTR_TYPE_NAME, status);
-	/*get its name and hence TDO*/
-	/*Now get the Actual TDO */
+	/* Get the Actual TDO */
+	OCIAttrGet_parmdp(imp_sth,parm, &type_ref, 0, OCI_ATTR_REF_TDO, status);
 
 	if (status != OCI_SUCCESS) {
-		oci_error(sth,imp_sth->errhp, status, "OCIAttrGet");
+		oci_error(sth, imp_sth->errhp, status, "OCIAttrGet");
 		return 0;
 	}
 
-	if (DBIS->debug >= 6 || dbd_verbose >= 6 ) {
-		PerlIO_printf(DBILOGFP, "Geting the properties of object named =%s at level %d\n",obj->type_name,level);
-	}
-	
-	OCITypeByName_log_stat(imp_sth->envhp,imp_sth->errhp,imp_sth->svchp,obj->type_name,obj->type_namel,&obj->tdo,status);
-	
+	OCITypeByRef_log_stat(imp_sth->envhp,imp_sth->errhp,type_ref,&obj->tdo,status);
+
 	if (status != OCI_SUCCESS) {
-		oci_error(sth, imp_sth->errhp, status, "OCITypeByName");
+		oci_error(sth, imp_sth->errhp, status, "OCITypeByRef");
 		return 0;
 	}
+
+	return describe_obj_by_tdo(sth, imp_sth, obj, level);
+	}
+
+int
+describe_obj_by_tdo(SV *sth,imp_sth_t *imp_sth,fbh_obj_t *obj,int level ) {
+	dTHX;
+	sword status;
+	text *type_name, *schema_name;
+	ub4  type_namel, schema_namel;
+
+
 	OCIDescribeAny_log_stat(imp_sth->svchp,imp_sth->errhp,obj->tdo,(ub4)0,OCI_OTYPE_PTR,(ub1)1,OCI_PTYPE_TYPE,imp_sth->dschp,status);
 	/*we have the Actual TDO  so lets see what it is made up of by a describe*/
-	
+
 	if (status != OCI_SUCCESS) {
 		oci_error(sth,imp_sth->errhp, status, "OCIDescribeAny");
 		return 0;
 	}
 
 	OCIAttrGet_parmap(imp_sth, imp_sth->dschp,OCI_HTYPE_DESCRIBE,  &obj->parmdp, 0, status);
-	
+
 	if (status != OCI_SUCCESS) {
 		oci_error(sth,imp_sth->errhp, status, "OCIAttrGet");
 		return 0;
@@ -2313,15 +2412,39 @@
 
 	/*and we store it in the object's paramdp for now*/
 
-	OCIAttrGet_parmdp(imp_sth,  obj->parmdp, (dvoid *)&obj->typecode, 0, OCI_ATTR_TYPECODE, status);
+	OCIAttrGet_parmdp(imp_sth, obj->parmdp, &schema_name, &schema_namel, OCI_ATTR_SCHEMA_NAME, status);
+
+	if (status != OCI_SUCCESS) {
+		oci_error(sth,imp_sth->errhp, status, "OCIAttrGet");
+		return 0;
+	}
+
+	OCIAttrGet_parmdp(imp_sth, obj->parmdp, &type_name, &type_namel, OCI_ATTR_NAME, status);
+
+	if (status != OCI_SUCCESS) {
+		oci_error(sth,imp_sth->errhp, status, "OCIAttrGet");
+		return 0;
+	}
+
+	/* make full type_name: schema_name + "." + type_name */
+	obj->full_type_name = newSVpv((char*)schema_name, schema_namel);
+	sv_catpvn(obj->full_type_name, ".", 1);
+	sv_catpvn(obj->full_type_name, (char*)type_name, type_namel);
+	obj->type_name = (text*)SvPV(obj->full_type_name,na);
 
 	/*we need to know its type code*/
 
+	OCIAttrGet_parmdp(imp_sth, obj->parmdp, (dvoid *)&obj->typecode, 0, OCI_ATTR_TYPECODE, status);
+
 	if (status != OCI_SUCCESS) {
 		oci_error(sth,imp_sth->errhp, status, "OCIAttrGet");
 		return 0;
 	}
 
+	if (DBIS->debug >= 6 || dbd_verbose >= 6 ) {
+		PerlIO_printf(DBILOGFP, "Geting the properties of object named =%s at level %d\n",obj->type_name,level);
+	}
+
 	if (obj->typecode == OCI_TYPECODE_OBJECT){
 		OCIParam *list_attr= (OCIParam *) 0;
 		ub2	  pos;
@@ -2755,11 +2878,11 @@
 					fbh->disize 		= fbh->disize+long_readlen; /*user set max value for the fetch*/
 					if (fbh->dbtype == ORA_CLOB){
 				  		fbh->ftype = SQLT_CHR;
-				  	} 
+				  	}
 				  	else {
 				  		fbh->ftype = SQLT_LVB; /*Binary form seems this is the only value where we cna get the length correctly*/
 				  	}
-				} 
+				}
 				else if (imp_sth->clbk_lob){ /*get by peice with callback a slow*/
 					fbh->clbk_lob	  = 1;
 					fbh->define_mode	= OCI_DYNAMIC_FETCH; /* piecwise fetch*/
@@ -2775,7 +2898,7 @@
 						fbh->ftype = SQLT_BIN; /*other Binary*/
 					}
 					fbh->fetch_func = fetch_clbk_lob;
-				} 
+				}
 				else if (imp_sth->piece_lob){ /*get by peice with polling slowest*/
 					fbh->piece_lob	  = 1;
 					fbh->define_mode	= OCI_DYNAMIC_FETCH; /* piecwise fetch*/
@@ -2792,7 +2915,7 @@
 						fbh->ftype = SQLT_BIN; /*other Binary */
 					}
 					fbh->fetch_func = fetch_get_piece;
-				} 
+				}
 				else { /*auto lob fetch with locator by far the fastest*/
 					fbh->disize = fbh->dbsize *10 ;	/* XXX! */
 					fbh->fetch_func = (imp_sth->auto_lob) ? fetch_func_autolob : fetch_func_getrefpv;
@@ -2996,14 +3119,14 @@
 
 	for(i=0; i < num_fields; ++i) {
 		imp_fbh_t *fbh = &imp_sth->fbh[i];
-		if (fbh->fetch_cleanup) 
+		if (fbh->fetch_cleanup)
 			fbh->fetch_cleanup(sth, fbh);
 	}
 
 	if (ora_fetchtest && DBIc_ROW_COUNT(imp_sth)>0) {
 		--ora_fetchtest; /* trick for testing performance */
 		status = OCI_SUCCESS;
-	} 
+	}
 	else {
 		if (DBIS->debug >= 3 || dbd_verbose >= 3 ){
 			PerlIO_printf(DBILOGFP, "	dbd_st_fetch %d fields...\n", DBIc_NUM_FIELDS(imp_sth));
@@ -3040,7 +3163,7 @@
 					status=OCI_SUCCESS;
 				else
 					status=imp_sth->rs_array_status;
-			} 
+			}
 			else {
 				OCIStmtFetch_log_stat(imp_sth->stmhp, imp_sth->errhp,1,(ub2)OCI_FETCH_NEXT, OCI_DEFAULT, status);
 				imp_sth->rs_array_idx=0;
@@ -3081,7 +3204,7 @@
 
 	ChopBlanks = DBIc_has(imp_sth, DBIcf_ChopBlanks);
 	err = 0;
-	
+
 	for(i=0; i < num_fields; ++i) {
 		imp_fbh_t *fbh		= &imp_sth->fbh[i];
 		fb_ary_t *fb_ary	= fbh->fb_ary;
@@ -3116,7 +3239,7 @@
  				if (!fbh->fetch_func(sth, fbh, sv)){
 					++err;	/* fetch_func already called oci_error */
 				}
-		  	} 
+		  	}
 		  	else {
 				int datalen = fb_ary->arlen[imp_sth->rs_array_idx];
 				char *p = (char*)row_data;
@@ -3125,7 +3248,7 @@
 						Seems I have to use SQLT_LVB to get the length all other will fail*/
 					datalen = *(ub4*)row_data;
 					sv_setpvn(sv, (char*)row_data+ sizeof(ub4), datalen);
-				} 
+				}
 				else {
 					if (ChopBlanks && fbh->dbtype == 96) {
 						while(datalen && p[datalen - 1]==' ')
@@ -3138,10 +3261,10 @@
 				}
 			}
 
-		} 
+		}
 		else if (rc == 1405) {	/* field is null - return undef	*/
 			sv_set_undef(sv);
-		} 
+		}
 		else {  /* See odefin rcode arg description in OCI docs	*/
 			char buf[200];
 			char *hint = "";
@@ -3160,7 +3283,7 @@
 					hint = ", LongReadLen too small and/or LongTruncOk not set";
 				}
 
-			} 
+			}
 			else {	/* set field that caused error to undef */
 				sv_set_undef(sv);
 			}
@@ -3196,15 +3319,15 @@
 	if (**uidp == '\0' && **pwdp == '\0') {
 		return OCI_CRED_EXT;
 	}
-	
+
 	OCIAttrSet_log_stat(imp_dbh->authp, OCI_HTYPE_SESSION,
 			*uidp, strlen(*uidp),
 			(ub4) OCI_ATTR_USERNAME, imp_dbh->errhp, status);
-	
+
 	OCIAttrSet_log_stat(imp_dbh->authp, OCI_HTYPE_SESSION,
 			(strlen(*pwdp)) ? *pwdp : NULL, strlen(*pwdp),
 			(ub4) OCI_ATTR_PASSWORD, imp_dbh->errhp, status);
-	
+
 	return OCI_CRED_RDBMS;
 }
 
@@ -3402,7 +3525,7 @@
 
 	OCIDescribeAny_log_stat(imp_sth->svchp, errhp, tablename, strlen(tablename),
 		(ub1)OCI_OTYPE_NAME, (ub1)1, (ub1)OCI_PTYPE_TABLE, imp_sth->dschp, status);
-	
+
 	if (status != OCI_SUCCESS) {
 	/* XXX this OCI_PTYPE_TABLE->OCI_PTYPE_VIEW fallback should actually be	*/
 	/* a loop that includes synonyms etc */
@@ -3420,12 +3543,12 @@
 		OCIAttrGet_log_stat(parmhp, OCI_DTYPE_PARAM,
 				&numcols, 0, OCI_ATTR_NUM_COLS, errhp, status);
 	}
-	
+
 	if (!status ) {
 		OCIAttrGet_log_stat(parmhp, OCI_DTYPE_PARAM,
 				&collisthd, 0, OCI_ATTR_LIST_COLUMNS, errhp, status);
 	}
-	
+
 	if (status != OCI_SUCCESS) {
 		OCIHandleFree_log_stat(imp_sth->dschp, OCI_HTYPE_DESCRIBE, status);
 		return oci_error(sth, errhp, status, "OCIDescribeAny/OCIAttrGet/LOB refetch");
@@ -3446,44 +3569,44 @@
 							OCI_ATTR_DATA_TYPE, errhp, status);
 		if (status)
 			break;
-			
+
 		OCIAttrGet_log_stat(colhd, OCI_DTYPE_PARAM, &col_name, &col_name_len,
 				OCI_ATTR_NAME, errhp, status);
 		if (status)
 			break;
-			
+
 		if (DBIS->debug >= 3 || dbd_verbose >= 3 )
 			PerlIO_printf(DBILOGFP, "		lob refetch table col %d: '%.*s' otype %d\n",
 				(int)i, (int)col_name_len,col_name, col_dbtype);
-				
+
 		if (col_dbtype != SQLT_CLOB && col_dbtype != SQLT_BLOB)
 			continue;
-			
+
 		if (!lob_cols_hv)
 			lob_cols_hv = newHV();
-	
+
 		sv = newSViv(col_dbtype);
 		(void)sv_setpvn(sv, col_name, col_name_len);
-	
+
 		if (CSFORM_IMPLIES_UTF8(SQLCS_IMPLICIT))
 			SvUTF8_on(sv);
-		
+
 		(void)SvIOK_on(sv);	/* "what a wonderful hack!" */
 		(void)hv_store(lob_cols_hv, col_name,col_name_len, sv,0);
 		OCIDescriptorFree(colhd, OCI_DTYPE_PARAM);
 		colhd = NULL;
 	}
-		
+
 	if (colhd)
 		OCIDescriptorFree(colhd, OCI_DTYPE_PARAM);
-		
+
 	if (status != OCI_SUCCESS) {
 		oci_error(sth, errhp, status,
 			"OCIDescribeAny/OCIParamGet/OCIAttrGet/LOB refetch");
 		OCIHandleFree_log_stat(imp_sth->dschp, OCI_HTYPE_DESCRIBE, status);
 		return 0;
 	}
-		
+
 	if (!lob_cols_hv)
 		return oci_error(sth, errhp, OCI_ERROR,
 			"LOB refetch failed, no lobs in table");
@@ -3505,15 +3628,15 @@
 	while( (sv = hv_iternextsv(imp_sth->all_params_hv, &p, &i)) != NULL ) {
 		int matched = 0;
 		phs_t *phs = (phs_t*)(void*)SvPVX(sv);
-	
+
 		if (sv == &sv_undef || !phs)
 			croak("panic: unbound params");
-		
+
 		if (phs->ftype != SQLT_CLOB && phs->ftype != SQLT_BLOB)
 			continue;
-	
+
 		hv_iterinit(lob_cols_hv);
-		
+
 		while( (sv = hv_iternextsv(lob_cols_hv, &p, &i)) != NULL ) {
 			char sql_field[200];
 			if (phs->ora_field) {	/* must match this phs by field name	*/
@@ -3572,7 +3695,7 @@
 		}
 	}
 	sv_free((SV*)lob_cols_hv);
-	
+
 	if (unmatched_params) {
 		Safefree(lr);
 		return oci_error(sth, errhp, OCI_ERROR,
@@ -3594,7 +3717,7 @@
 	OCIStmtPrepare_log_stat(lr->stmthp, errhp,
 		(text*)SvPVX(sql_select), SvCUR(sql_select), OCI_NTV_SYNTAX,
 			OCI_DEFAULT, status);
-	
+
 	if (status != OCI_SUCCESS) {
 		OCIHandleFree(lr->stmthp, OCI_HTYPE_STMT);
 		Safefree(lr);
@@ -3693,12 +3816,12 @@
 	lr = imp_sth->lob_refetch;
 
 	OCIAttrGet_stmhp_stat(imp_sth, lr->rowid, 0, OCI_ATTR_ROWID,status);
-	
+
 	if (status != OCI_SUCCESS)
 		return oci_error(sth, errhp, status, "OCIAttrGet OCI_ATTR_ROWID /LOB refetch");
-	
+
 	OCIStmtExecute_log_stat(imp_sth->svchp, lr->stmthp, errhp,1, 0, NULL, NULL, OCI_DEFAULT, status);	/* execute and fetch */
-	
+
 	if (status != OCI_SUCCESS)
 		return oci_error(sth, errhp, status,
 
@@ -3711,7 +3834,7 @@
 		ub4 amtp;
 
 		if(SvUPGRADE(phs->sv, SVt_PV)){/* For GCC not to warn on unused result */ };	/* just in case */
-	
+
 		amtp = SvCUR(phs->sv);		/* XXX UTF8? */
 		if (rc == 1405) {		/* NULL - return undef */
 			sv_set_undef(phs->sv);
@@ -3747,22 +3870,22 @@
 			if (status != OCI_SUCCESS) {
 				return oci_error(sth, errhp, status, "OCILobWrite in post_execute_lobs");
 			}
-			
+
 		} else {			/* amtp==0 so truncate LOB to zero length */
 			OCILobTrim_log_stat(imp_sth->svchp, errhp, (OCILobLocator*)fbh->desc_h, 0, status);
-			
+
 			if (status != OCI_SUCCESS) {
 				return oci_error(sth, errhp, status, "OCILobTrim in post_execute_lobs");
 			}
-			
+
 		}
-		
+
 		if (DBIS->debug >= 3 || dbd_verbose >= 3 )
 			PerlIO_printf(DBILOGFP,
 			"		lob refetch %d for '%s' param: ftype %d, len %ld: %s %s\n",
 			i+1,fbh->name, fbh->dbtype, ul_t(amtp),
 			(rc==1405 ? "NULL" : (amtp > 0) ? "LobWrite" : "LobTrim"), oci_status_name(status));
-			
+
 		if (status != OCI_SUCCESS) {
 			return oci_error(sth, errhp, status, "OCILobTrim/OCILobWrite/LOB refetch");
 		}
@@ -3784,10 +3907,10 @@
 	if (lr->rowid)
 		OCIDescriptorFree(lr->rowid, OCI_DTYPE_ROWID);
 	OCIHandleFree_log_stat(lr->stmthp, OCI_HTYPE_STMT, status);
-	
+
 	if (status != OCI_SUCCESS)
 		oci_error(sth, imp_sth->errhp, status, "ora_free_lob_refetch/OCIHandleFree");
-		
+
 	for(i=0; i < lr->num_fields; ++i) {
 		imp_fbh_t *fbh = &lr->fbh_ary[i];
 		ora_free_fbh_contents(fbh);

Modified: dbd-oracle/trunk/ocitrace.h
==============================================================================
--- dbd-oracle/trunk/ocitrace.h	(original)
+++ dbd-oracle/trunk/ocitrace.h	Wed Jan 14 07:28:59 2009
@@ -191,6 +191,14 @@
 			 OciTp, (void*)envhp, (void*)errhp, (void*)svchp, (char*)(p1),(l),oci_status_name(stat)),stat \
 	: stat
 
+#define OCITypeByRef_log_stat(envhp,errhp,ref,tdo,stat)\
+	stat = OCITypeByRef(envhp,errhp,ref,OCI_DURATION_TRANS,OCI_TYPEGET_ALL,tdo);\
+	(DBD_OCI_TRACEON) \
+		?	PerlIO_printf(DBD_OCI_TRACEFP,\
+			 "%sTypeByRef(%p,%p,%p)=%s\n",\
+			 OciTp, (void*)envhp, (void*)errhp, (void*)ref,oci_status_name(stat)),stat \
+	: stat
+
 /* added by lab */
 #define OCILobCharSetId_log_stat( envhp, errhp, locp, csidp, stat ) \
 	stat = OCILobCharSetId( envhp, errhp, locp, csidp ); \
@@ -343,7 +351,7 @@
 #define OCIInitialize_log_stat(md,cp,mlf,rlf,mfp,stat)				 \
 	stat=OCIInitialize(md,cp,mlf,rlf,mfp);				\
 	(DBD_OCI_TRACEON) ? PerlIO_printf(DBD_OCI_TRACEFP,			\
-		"%sInitialize(%lu,with mode =%s %p,%p,%p,%p)=%s\n",				\
+		"%sInitialize(with mode =%s %lu,%p,%p,%p,%p)=%s\n",				\
 		OciTp, oci_mode(md),ul_t(md),(void*)cp,(void*)mlf,(void*)rlf,(void*)mfp,	\
 		oci_status_name(stat)),stat : stat
 

Added: dbd-oracle/trunk/t/58object.t
==============================================================================
--- (empty file)
+++ dbd-oracle/trunk/t/58object.t	Wed Jan 14 07:28:59 2009
@@ -0,0 +1,153 @@
+#!perl -w
+
+use DBI;
+use DBD::Oracle qw(ORA_RSET SQLCS_NCHAR);
+use strict;
+use Data::Dumper;
+
+use Test::More tests => 34;
+unshift @INC ,'t';
+require 'nchar_test_lib.pl';
+
+$| = 1;
+
+BEGIN {
+	use_ok('DBI');
+}
+
+$ENV{NLS_DATE_FORMAT} = 'YYYY-MM-DD"T"HH24:MI:SS';
+
+# create a database handle
+my $dsn = oracle_test_dsn();
+my $dbuser = $ENV{ORACLE_USERID} || 'scott/tiger';
+my $dbh = DBI->connect($dsn, $dbuser, '',{ RaiseError=>1, 
+					AutoCommit=>1,
+					PrintError => 0,
+					 ora_objects => 1 });
+
+my ($schema) = $dbuser =~ m{^([^/]*)};
+
+# Test ora_objects flag 
+cmp_ok($dbh->{ora_objects}, 'eq', '1', 'ora_objects flag is set to 1');
+
+$dbh->{ora_objects} = 0;
+cmp_ok($dbh->{ora_objects}, 'eq', '0', 'ora_objects flag is set to 0');
+
+# check that our db handle is good
+isa_ok($dbh, "DBI::db");
+
+my $obj_prefix = "dbd_test_";
+my $super_type = "${obj_prefix}_type_A";
+my $sub_type = "${obj_prefix}_type_B";
+my $table = "${obj_prefix}_obj_table";
+
+sub drop_test_objects {
+    for my $obj ("TABLE $table", "TYPE $sub_type", "TYPE $super_type") {
+        #do not warn if already there
+        eval {
+            local $dbh->{PrintError} = 0;
+            $dbh->do(qq{drop $obj});
+        };
+    }
+}
+
+&drop_test_objects;
+
+$dbh->do(qq{ CREATE OR REPLACE TYPE $super_type AS OBJECT (
+                num     INTEGER,
+                name    VARCHAR2(20)
+            ) NOT FINAL }) or die $dbh->errstr;
+
+$dbh->do(qq{ CREATE OR REPLACE TYPE $sub_type UNDER $super_type (
+                datetime  DATE,
+                amount    NUMERIC(10,5)
+            ) NOT FINAL }) or die $dbh->errstr;
+$dbh->do(qq{ CREATE TABLE $table (id INTEGER, obj $super_type) })
+            or die $dbh->errstr;
+$dbh->do(qq{ INSERT INTO $table VALUES (1, $super_type(13, 'obj1')) })
+            or die $dbh->errstr;
+$dbh->do(qq{ INSERT INTO $table VALUES (2, $sub_type(NULL, 'obj2', 
+                    TO_DATE('2004-11-30 14:27:18', 'YYYY-MM-DD HH24:MI:SS'),
+                    12345.6789)) }
+            ) or die $dbh->errstr;
+$dbh->do(qq{ INSERT INTO $table VALUES (3, $sub_type(5, 'obj3', NULL, 777.666)) }
+            ) or die $dbh->errstr;
+
+# Test old (backward compatible) interface 
+
+# test select testing objects 
+my $sth = $dbh->prepare("select * from $table order by id");
+ok ($sth, 'old: Prepare select');
+ok ($sth->execute(), 'old: Execute select');
+
+my @row1 = $sth->fetchrow();
+ok (scalar @row1, 'old: Fetch first row');
+cmp_ok(ref $row1[1], 'eq', 'ARRAY', 'old: Row 1 column 2 is an ARRAY');
+cmp_ok(scalar(@{$row1[1]}), '==', 2, 'old: Row 1 column 2 is has 2 elements');
+
+my @row2 = $sth->fetchrow();
+ok (scalar @row2, 'old: Fetch second row');
+cmp_ok(ref $row2[1], 'eq', 'ARRAY', 'old: Row 2 column 2 is an ARRAY');
+cmp_ok(scalar(@{$row2[1]}), '==', 2, 'old: Row 2 column 2 is has 2 elements');
+
+my @row3 = $sth->fetchrow();
+ok (scalar @row3, 'old: Fetch third row');
+cmp_ok(ref $row3[1], 'eq', 'ARRAY', 'old: Row 3 column 2 is an ARRAY');
+cmp_ok(scalar(@{$row3[1]}), '==', 2, 'old: Row 3 column 2 is has 2 elements');
+
+ok (!$sth->fetchrow(), 'old: No more rows expected');
+
+#print STDERR Dumper(\@row1, \@row2, \@row3);
+
+# Test new (extended) object interface 
+
+# enable extended object support 
+$dbh->{ora_objects} = 1;
+
+# test select testing objects - in extended mode 
+$sth = $dbh->prepare("select * from $table order by id");
+ok ($sth, 'new: Prepare select');
+ok ($sth->execute(), 'new: Execute select');
+
+
+@row1 = $sth->fetchrow();
+ok (scalar @row1, 'new: Fetch first row');
+cmp_ok(ref $row1[1], 'eq', 'DBD::Oracle::Object', 'new: Row 1 column 2 is an DBD:Oracle::Object');
+cmp_ok(uc $row1[1]->type_name, "eq", uc "$schema.$super_type", "new: Row 1 column 2 object type");
+is_deeply([$row1[1]->attributes], ['NUM', 13, 'NAME', 'obj1'], "new: Row 1 column 2 object attributes");
+
+@row2 = $sth->fetchrow();
+ok (scalar @row2, 'new: Fetch second row');
+cmp_ok(ref $row2[1], 'eq', 'DBD::Oracle::Object', 'new: Row 2 column 2 is an DBD::Oracle::Object');
+cmp_ok(uc $row2[1]->type_name, "eq", uc "$schema.$sub_type", "new: Row 2 column 2 object type");
+is_deeply([$row2[1]->attributes], ['NUM', undef, 'NAME', 'obj2', 
+            'DATETIME', '2004-11-30T14:27:18', 'AMOUNT', '12345.6789'], "new: Row 1 column 2 object attributes");
+
+@row3 = $sth->fetchrow();
+ok (scalar @row3, 'new: Fetch third row');
+cmp_ok(ref $row3[1], 'eq', 'DBD::Oracle::Object', 'new: Row 3 column 2 is an DBD::Oracle::Object');
+cmp_ok(uc $row3[1]->type_name, "eq", uc "$schema.$sub_type", "new: Row 3 column 2 object type");
+is_deeply([$row3[1]->attributes], ['NUM', 5, 'NAME', 'obj3', 
+            'DATETIME', undef, 'AMOUNT', '777.666'], "new: Row 1 column 2 object attributes");
+
+ok (!$sth->fetchrow(), 'new: No more rows expected');
+
+#print STDERR Dumper(\@row1, \@row2, \@row3);
+
+# Test DBD::Oracle::Object 
+my $obj = $row3[1];
+my $expected_hash = {
+        NUM         => 5,
+        NAME        => 'obj3',
+        DATETIME    => undef,
+        AMOUNT      => 777.666,
+    };
+is_deeply($obj->attr_hash, $expected_hash, 'DBD::Oracle::Object->attr_hash');
+is_deeply($obj->attr, $expected_hash, 'DBD::Oracle::Object->attr');
+is($obj->attr("NAME"), 'obj3', 'DBD::Oracle::Object->attr("NAME")');
+
+#cleanup 
+&drop_test_objects;
+$dbh->disconnect;
+
+1;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.