A Framework for Schema Evolution by Meta Object ... - Semantic Scholar

2 downloads 9498 Views 249KB Size Report
object algebra COOL in the same way on the meta level as on the primary object ... tions are described by their signature (the name, domain- and range types).
A Framework for Schema Evolution by Meta Object Manipulation Markus Tresch

Department of Computer Science, Information Systems { Databases ETH Zurich, CH-8092 Zurich, Switzerland e-mail: [email protected] Abstract

In this paper we address the problem of schema evolution in object-oriented database systems. Most currently available database prototypes either completely lack schema evolution facilities, or o er a restricted set of special purpose schema evolution operators supporting simple schema changes. Our approach is di erent. We consider schema objects as objects like others and have the meta schema fully available at run-time. Thus any operator of our object manipulation and query algebra can be applied to meta objects in the same way as they are used for data objects. We discuss the additional problems that arise when treating schema objects as normal objects and making the algebra work on the meta schema as well. Furthermore, we overview how to solve these problems on di erent levels of implementing schema evolution.

1 Introduction The logical structure of the data stored in a database is de ned by the database schema. In object-oriented database management systems, this schema consists of types (arranged in an inheritance lattice) and classes (forming a subclass hierarchy). In general, such a logical structure is likely to undergo changes during the lifetime of a database. According to new requirements, it may dynamically change in several ways, even if the database is already populated with objects. In this paper, we summarize in the term schema evolution several types of possible changes to the logical database schema: original schema design, schema updates during database lifetime, and schema integration. Most current object-oriented database system products and prototypes do not allow free dynamic changes to the schema with populated databases. The reason is that either the schema information is not available in an appropriate way, or it is simply a read-only data-dictionary, that cannot be changed such that the database is kept consistent. Most published in: Proceedings of the 3rd Int'l Workshop on Foundations of Models and Languages for Data and Objects, Aigen, Austria, September 1991. Tech. Report, Institut fur Informatik, TU Clausthal. 

1

often in these systems, after the design of the database schema is committed, no more changes to the structure are allowed. More advanced systems support schema update facilities. There is no longer a clear cut between the design and application phases. Nevertheless, schema evolution possibilities are very limited and are normally restricted to simple changes, realized by a given set of special purpose schema update operators. Furthermore, changing the schema information is often only allowed, if the database is empty. If they allow to modify populated databases, they either include a data migration utility to adapt existing data objects to the changed schema, or some other mechanism has to ensure consistency between data and structure. An early investigation of changing types in populated database exists for the ENCORE database management system [SZ86, SZ87]. This work addresses the e ects of type changes to objects and to programs that use objects of the type. The rst systematic analysis of desirable schema evolution possibilities was done for the ORION data model in [BKKK87, BCG 87]. A set of all necessary schema updates was listed and organized in a taxonomy. Similar enumerations can also be found for the O [Zic90] and the GemStone DBMS [PS87], actually forming a subset of ORION's taxonomy. The schema update primitives of the above taxonomy are realized as special purpose operators for schema management. That is, for every possibility there is a function. So nally, the collection of these operators build a "schema update language". Our approach is di erent in that we try to use a transparent meta level: In our objectoriented data model COCOON, we consider schema objects as objects like others, and have the meta types and meta classes available at any time. We allow the use of our object algebra COOL in the same way on the meta level as on the primary object level. So there is no di erence in the syntax of the descriptive algebra operators, whether they query or update data-, schema-, or meta objects. In fact, representation of the meta schema within the same model is an old idea. However, usually such meta data serve only for documentation purposes. That is, only queries are allowed. In contrast, here we investigate the feasibility of updates to meta objects and their propagation to the instances. The paper is organized as follows. In the next section, we sketch our object model and its algebra. We introduce the schema and meta level objects in Section 3. In Section 4 we discuss the problems that arise, if we allow the algebra to work on the meta objects. +

2

2 The COCOON Object Model 2.1 Basic Concepts

The COCOON object model used here is an object-function model in the sense of [Bee89]. As described in [SS90, SLT91] it is a core object model, meaning that it is restricted to the following essential constituents:  Objects are instances of abstract types, speci ed by a set of applicable operations. In contrast, data are instances of concrete types, that can be atomic (numbers, strings) or constructed (tuples, sets).  Functions are the generalized abstraction of attributes (stored or computed), update methods, and relationships between objects. They can be single- or set-valued. Func2

tions are described by their signature (the name, domain- and range types).  Types are normally named and are de ned by a set of functions that are applicable to their instances. They are arranged in a type lattice, where subtypes inherit all functions from their supertypes. The type object is the prede ned root of the lattice. Objects can be instances of several types at the same time (multiple instantiation).  Classes are strictly distinguished from types. Classes are typed collections of objects. They form a subclass hierarchy, meaning that the collection of objects in a subclass is included in the collection of its superclasses. The class Objects is the prede ned root of the subclass hierarchy. Objects can be member of multiple classes at the same time (multiple class membership). One important feature of our model that is usually not found in other models is class predicates. Constraints can be attached to a class, that must be satis ed by every member object of the class. Class predicates may either be necessary or necessary and sucient conditions. Classes with necessary and sucient predicates are populated automatically: whenever objects are added to their superclass(es) and the predicate evaluates to true, the new objects are included in this class, too. As an example, consider the type person and the two classes Persons and Youngs below. Both classes have the same member type person. The class Youngs is de ned as a subclass of Persons, holding exactly those persons that satisfy the class predicate age < 30. The selector all in the where clause indicates that the class predicate is necessary and sucient (in contrast to "some " meaning only necessary), such that the member objects of Youngs are automatically computed from those of Persons.

de ne type person isa object = name: string, age: integer ; de ne type employee isa person = salary: integer, empl: company inverse sta ; de ne type company isa object = name, city: string,sta : set of employee inverse empl; de ne class Persons : person some Objects ; de ne class Youngs : person all Persons where age, where

1. O is the nite set of persistent objects in the database, and 2.  = f < fi ; oj ; vij > g is the state of the database, such that vij is the value, returned by applying function fi to object oj , where fi 2 F ; oj 2 O. 2 A closer look at the persistent objects O of a database shows, that they are built up of three sort of objects: meta, schema, and data objects (O = Ometa [ Oschema [ Odata ): 4



meta objects: The objects, building the database kernel. They are application independent and always part of the database. They are composed of meta types TM , meta functions FM , meta classes CM , the root of the type inheritance lattice (object), and the root of the subclass hierarchy (Objects). Ometa

=

object ; type ; Functions{z; Classes ; Views} | ; function {z ; class}; Objects ; Types |

f

T

M

M

C

tname ; inherited ; local ; functs ; supert ; fname ; dom ; ran ; setval ; inverse ; computed ; cname ; mtype {z ; superc ; sucient ; pred ; extent} |

g

M

F



schema objects: The objects de ning the schema of the database application. They are created as instances of meta types and consist of the application types TA , the application functions FA, and the application classes CA . Oschema

= ft| ; :{z: :; tk}; f| ; : {z : :; fm}; c| ; :{z: :; c}lg 1

1

A

A

T



1

F

C

A

data objects: The primary level data objects, representing the user data that are stored in the database. They are created as instances of application types. Odata

= fo ; : : :; on g 1

A valid "empty" database holds only the meta objects (called the kernel). Later, the database design process adds schema objects, and the use of the application generates nally data objects. Figure 1 gives an overview on types and its instances on the three di erent levels.

type

function

person company

john paul

class

Persons

Types

Functions

Youngs Companies

Classes

Views

Meta Objects

Schema Objects

Data Objects

ibm dec

Figure 1: Instance-of Relationship between Meta, Schema, and Data Level Objects 5

3.2 Meta Schema

A database schema is a representation of the structure (syntax), semantics, and constraints on the use of a database in the data model. More formally, the schema of a database DB = < O;  > is given as the triple of types, functions, and classes de ned in the database: SDB = < T ; F ; C >, with T ; F ; C  O. Following the above de nition, we de ne the meta schema as a special database schema: the schema of the meta database, that is, the triple SM = < TM ; FM ; CM >, where TM = ftype ; function ; class g, FM = ftname ; inherited ; : : : ; extent g, and CM = f Types, Functions, Classes, Views g. For the speci cation of the meta database in COOL notation, see Appendix A. Figure 2 below shows the hierarchy of meta types (with their meta functions) and meta classes. object

Objects

extent

tname inv

dom type

function

fname setval computed

inverse

pred

local

class

cname sufficient pred

Types

Functions

Classes

inherited supert

ran

superc Views

mtype

Figure 2: Hierarchy of Meta Types and Meta Classes

4 Manipulation of Meta Level Objects The presented framework gives a formal de nition of databases, where (i) objects are treated uniformly, regardingless whether they belong to the data, schema, or meta schema level, and (ii) the meta schema is well de ned and fully available. These are both prerequisites for making use of the COOL language to retrieve and modify meta objects. Querying the meta schema of Appendix A works straight forward without additional e ort. As an example, query Q below selects all types that are not supertype of any other type (being exactly the leaves of the type lattice), and Q searchs for all functions with equal domain and range type, listing the name of the function and of the domain type. Q : select [; = select [t 2 supert(s)](s:Types)] (t:Types); Q : project [name,name(dom)] (select [dom(f)=ran(f)] (f:Functions)); The next step is to perform updates on meta objects. In contrast to the query language, direct meta object manipulation yields side e ects, that must be regarded carefully. Consider update U , creating a new single-valued function with name address. Because its 6 1

2

1

2

1

domain type is person, the new function will be added to functs(person). According to the type lattice, the function is inherited from type person to all subtypes, e.g., employee. What if there already exists a function with the same name in one of these classes? U : addr:=insert [fname:='address',dom:=person,ran:=string,setval:=false](Functions); U : delete (person); Update U deletes an existing type from the database. If there are some functions with the destroyed type as their domain (e.g., name, age), should they be deleted too? When the type person no longer exists, what will be the member type of the classes Persons and Youngs? What will be the new supertype of employee? What happens to the instances of the deleted type? Should they be destroyed too, or are they still kept as instances of object, for example? These two examples show, that manipulating one single schema object may call for several other operations, or otherwise results in an incorrect database state. In the sequel, we focus on some particular details, namely the following three problem areas: (i) completeness of update possibilities, (ii) correctness of schema changes, and (iii) propagation of schema level updates to the instance level. 1

2

2

4.1 Completeness

Completeness concerns the problem whether all desirable schema changes can be performed using the proposed operations. For this purpose, we use the ORION taxonomy of schema changes, which is usually considered to be complete . 1

Proposition 1. Direct manipulation of the COCOON meta schema by COOL opera-

tors provides complete schema change possibilities in the sense of the ORION taxonomy. 2

This can easily be seen, by adapting the ORION taxonomy to the COCOON model (see the table below), and showing that each entry can be performed by a sequence of COOL operators applied on meta objects. As an example, notice that schema change (1.1) is performed by sample update U and schema change (3.2) by update U . This taxonomy is not only complete, but also minimal. If there were only changes on unpopulated databases, the number of needed schema change primitives would be even smaller. E.g., deleting a function, and then creating a new one with di erent name equals (on the schema level) to renaming a function. In contrast, on the instance level data is lost. A more detailed discussion of the COCOON taxonomy is contained in [Tre90]. 1

2

4.2 Correctness

A database schema is a well de ned collection of types and classes. Changing one of the meta objects alone may result in an incorrect schema. To ensure consistency, constraints have to be de ned for the schema objects, that must be preserved during schema object manipulation. 1A "proof" is given in [BKKK87] based on a simple formal model, called property inheritance graph

(PIG), which captures the essential characteristics of the ORION schema evolution model.

7

Consistency Constraints Propagation Type- Unique Classi - Range Instance Object Lattice Naming cation Closure Convers Reclass

Schema Updates Updating Types

(1.1) creation/deletion of a type object (1.1.1) create a new type object (1.1.2) delete an existing type object (1.2) changes to a type object (1.2.1) change the name (1.2.2) add a new inherited function (1.2.3) remove an inherited function

   

Updating Functions

(2.1) creation/deletion of a function object (2.1.1) create a new function object (2.1.2) delete a function object (2.2) changes to a function object (2.2.1) change the name (2.2.2) change the domain type (2.2.3) change the range type (2.2.4) change the set/single-valued tag (2.2.5) change the inverse function (2.2.6) change the computed/stored tag

  







 

 

p p p p



p p

 

p p p p

Updating Classes

(3.1) creation/deletion of a class object (3.1.1) create a new class object (3.1.2) delete an existing class object (3.2) changes to a class object (3.2.1) change the name (3.2.2) change the member type (3.2.3) add a new classi cation edge (3.2.4) remove a classi cation edge (3.2.5) change the su /necess tag (3.2.6) change the class predicate









- may be violated

p p

 

    p



p p p

- may be a ected

For this purpose, the ORION, O2, and GemStone data models provide a set of schema invariants. These are conditions that have to be satis ed before and after changing the schema. In O2 there is an interactive consistency checker, that proves for every schema update, whether structural or behavioral inconsistencies could arise. Similar, in our model there are four schema constraints, determining the basic characteristics of a COCOON schema:  type-lattice constraint: types are arranged in a function inheritance lattice, forming a directed acyclic graph (DAG) with root type object,  unique naming constraint: types and classes must have unique names in the database, and functions must have di erent names within their domain type,  classi cation constraint: classes build a subclass hierarchy with root class Objects, 8

p p p p

objects are automatically classi ed to subclasses, if they ful ll the class predicate, and the class has an all-selector,  closure constraint: domain, range, and member type must be existing types of the lattice. Whereas other systems satisfy these invariants by an appropriate implementation of their schema change operators, we have three di erent possibilities to guarantee correct schemas: (i) to nd an appropriate formal de nition of the model, such that many invariants are ensured automatically; (ii) to de ne constraints on the meta schema, that must be ful lled by every schema object; (iii) to use schema update transactions. Of course, we tend to include as much as possible into the de nition of the model. For example, the type-lattice constraint is ensured automatically by the way how we formally de ned our model: we consider types as (usually named) sets of functions, thus the position of a type in the lattice is implicitly de ned as    () functs ( )  functs ( ). In other words, whenever the de nition of a type changes, its position in the lattice is recalculated automatically. Or vice-versa, the subtype hierarchy can be manipulated by adding/removing functions applicable to a type. Other invariants are guaranteed by model-inherent constraints, namely as class predicates. For example, the unique naming rule is implemented by class predicates, as shown in the de nition of the meta classes in Appendix A. The table in Section 4.1 gives a complete overview of schema updates and their potential e ects on schema constraints. The third possibility of keeping a schema consistent, is to execute complete schema change transactions, that is sequences of COOL operators. This way can be chosen, if a single COOL operation alone cannot implement a schema transformation correctly, but a sequence of them can. For example, destroying a type like in update U , removes the functions name and age automatically from the class Functions, because they do not any more satisfy the class predicate (cf. Appendix A). To avoid deleting those functions together with the type, the following transaction could be run: 0

0

2

BOT set [dom:= ] (select [dom=person](Functions)); set [ran:= ] (select [ran=person](Functions)); set [mtype:= ] (select [mtype=person](Classes)); delete (person); EOT

4.3 Propagation

In a populated database, objects exist as instances of types and are collected in classes. Schema level updates, changing the de nition of types or classes, may have an impact on the existing instances. We distinguish in our model two di erent ways of schema change propagation:  instance conversion, where instances must be transformed to t into the modi ed type de nition (see also [SZ86, SZ87, TK89, LH90]); 9

object reclassi cation, where classi cation of objects must be reconsidered, according to changing class de nition (cf. [NR89a, NR89b]). The above table gives an overview on which schema changes propagate to data objects. Instance conversion is necessary for those changes manipulating a type or the type-lattice. As an example, consider again update U1. Instances of type person, that have been created before U1 was executed, do not have a value de ned for the function addr in the database. This is the reason why most existing systems demand such instances to be converted to the new type immediately after schema change. However, this is not necessary. Since a type may have many instances, such eager conversion of all objects after each schema modi cation may be to expensive. In contrast, a lazy conversion strategy delays any instance conversion as long as possible. With a slight modi cation of the formal de nitions of the COCOON model in [SLTS91] we can even build this lazy conversion into the model. The state function  : F ! (O ! O) returns for each function object f 2 F , the actual function O ! O from the database state: 8 > , if 9 < fi; oj ; vij > 2  and vij 2 ran (fi); < vij , if 6 9 < f ; o ; v > 2 , (fi)(oj ) := > ? : cast (vij ; tr ) , if 9 < fi i; ojj; vijij > 2  and vij 62 ran (fi ), with tr = ran (fi ). Applying (f ) to an object o yields the current value vij of f (o). It returns a null value (?), if there is no value actually de ned in the database state, and in addition, (fi)(oj ) transforms the retrieved value into another type, if it is not identical to the actual range type of fj . This third alternative was added to realize lazy conversion. Consider, for example, schema update (2.2.3) of the taxonomy, where the range of the age function could be changed from integer to date. When the age of a person is accessed next, the -operator detects a type inconsistency and tries to transform the existing agevalue to the new type. 

Other schema changes propagate by object reclassi cation. For example, changing the class predicate of class Youngs from age(p) < 30 to age(p) 6= 25 demands that some persons have to be removed from the class (those with age below 30 and not equal 25), and some persons have to be added to the class (those older than 30). In our framework, such reclassi cation is automatically performed. The de nition of the meta type class in Appendix A shows, that the extent of a class with an all-selector is computed from the members of its superclass(es) and the predicate.

5 Conclusion and Perspectives The contribution of this paper is threefold. We presented a formal framework, where meta and schema objects are treated as ordinary objects, and the schema is fully available to the user (see Appendix A). We de ned the semantics of the operators of our object-oriented algebra such that they can be applied on any object, independent of whether it belongs to the schema or data level. We showed, that this mechanism is powerful enough to realize schema evolution, because all schema updates given in a taxonomy can be implemented as a (sequence of) COOL operations applied on schema objects. 10

We discussed a couple of examples, showing that correctness of schema object manipulation and propagation to the instance level can be ensured on three di erent levels of implementation: (i) by an appropriate formalization of the data model and algebra; (ii) by model-inherent constraints (class predicates) on the meta schema; (iii) by schema update transactions, that is, sequences of statements to be executed together. Future work will address implementation issues of the presented framework. For this purpose, a prototype is under development, that realizes evolution of COCOON schemas on top of Oracle and the ONTOS object-oriented database system [TS91]. We expect to achieve further interesting results by investigating mixed level operations. Notice, that in this paper we simply presented queries and updates, operating on one single level. However, the sequence apply [ select [dom=person] (Functions) ] ( Persons ); searchs for all functions with domain type person and applies them on all objects in class Persons. Such a mixed level facility requires an apply operator that gives additional expressive power to the query language. On the other hand, static type checking becomes impossible in some cases.

Acknowledgement The author is indebted to Marc Scholl for numerous discussions and improving an earlier draft of this paper.

References

[BCG+ 87] J. Banerjee, H. Chou, J.F. Garza, W. Kim, D. Woelk, and N. Ballou. Data model issues for object-oriented applications. ACM Trans. on Oce Information Systems, 5(1):3{26, January 1987. [Bee89] C. Beeri. Formal models for object-oriented databases. In Kim et al. [KNN89], pages 370{395. [BKKK87] J. Banerjee, W. Kim, H.J. Kim, and H.F. Korth. Semantics and implementation of schema evolution in object-oriented databases. In ACM SIGMOD Record 1987, pages 311{322, San Francisco, CA, February 1987. ACM Press. [HM90] S. Hong and F. Maryanski. Using a meta model to represent object-oriented data models. In Proc. 6th Int'l IEEE Conf. on Data Engineering, pages 11 { 19, Los Angeles, USA, February 1990. IEEE Comp. Soc. Press. [KNN89] W. Kim, J.-M. Nicolas, and S. Nishio, editors. Proc. 1st Int'l Conf. on Deductive and ObjectOriented Databases (DOOD), Kyoto, December 1989. North-Holland. [LH90] B.S. Lerner and A.N. Habermann. Beyond schema evolution to database reorganization. In M. Meyrowitz, editor, Proc. joint Int'l Conf. OOPSLA / ECOOP, pages 67{76, Ottawa, Canada, October 1990. ACM Press. [NR89a] G.T. Nguyen and D. Rieu. Schema change propagation in object-oriented databases. In Information Processing 89. Proc. 11th IFIP World Computer Congress, pages 815{820, San Francisco, CA, August 1989. IFIP, North-Holland, Amsterdam. [NR89b] G.T. Nguyen and D. Rieu. Schema evolution in object-oriented database systems. Data & Knowledge Engineering, North-Holland, 4(1):43{67, July 1989.

11

[PS87]

D.J. Penney and J. Stein. Class modi cation in the GemStone object-oriented DBMS. In

Proc. Int'l Conf. on Object-Oriented Programming Systems and Languages (OOPSLA), pages

111{117. ACM Press, October 1987. [SLT91] M.H. Scholl, C. Laasch, and M. Tresch. Updatable views in object-oriented databases. In Proc. 2nd Int'l Conf. on Deductive and Object-Oriented Databases (DOOD), Munich, Germany, December 1991. [SLTS91] M.H. Scholl, C. Laasch, M. Tresch, and H.-J. Schek. The COCOON core object model. Technical report, ETH Zurich, Dept. of Computer Science, 1991. in preparation. [SS90] M.H. Scholl and H.-J. Schek. A relational object model. In Proc. 3rd Int'l Conf. on Database Theory (ICDT'90), Paris, 1990. [SZ86] A.H. Skarra and S.B. Zdonik. The management of changing types in an object-oriented database. In Proc. Int'l Conf. on Object-Oriented Programming Systems and Languages (OOPSLA), pages 483{495. ACM Press, September 1986. [SZ87] A.H. Skarra and S.B. Zdonik. Type evolution in an object-oriented database. In B. Shriver and P. Wegner, editors, Research Directions in Object-Oriented Programming, pages 393{413. MIT Press, 1987. [TK89] L. Tan and T. Katayame. Meta operations for type management in object-oriented databases. In Kim et al. [KNN89]. [Tre90] M. Tresch. Semantics of schema evolution in the COCOON object model. Manuscript, ETH Zurich, Dept. of Computer Science, 1990. [TS91] M. Tresch and M.H. Scholl. Implementing an object model on top of commercial database systems. In Proc. 3rd GI Workshop on Foundations of Database Systems, Volkse, Germany, May 1991. Technical Report 158, Dept. of Computer Science, ETH Zurich. [Zic90] R. Zicari. A framework for schema updates in an object-oriented database system. Report 90-025, Politechnico die Milano, Dipartimento di Elettronica, 1990.

A The Meta Schema The COCOON meta schema is composed of the meta types (type, function, class) and the meta classes (Types, Functions, Classes, Views). In COCOON notation, they are given as follows: type type isa object tname string inherited set of function local set of function inverse dom functs set of function supert set of type

=

:

:

: :

:

,

;

where: tname (t ) = nt inherited (t ) = ff ; : : :; fng local (t ) := ffi 2 F j dom(fi ) = tg functs (t ) := inherited (t ) [ local (t ) supert (t ) := fti 2 T jfuncts (ti )  functs (t)g 1

12

, ,

,

the name of the type the set of inherited functions the functions with type t as its domain the functions, applicable to t's instances the set of supertypes

type function isa object = fname :

,

string dom type inverse functs ran type setval boolean inverse function inverse inverse computed boolean where: fname (f ) = nf the name of the function dom (f ) = tD the type where the function is locally de ned, that is f 2 local (tD ), with tD 2 T ran (f ) = tR the range type of the function (tR 2 T ) setval (f ) = true=false the function is set-valued / single-valued inverse (f ) = fv =? another function which is inverse to f computed (f ) = true=false the value of f is computed / stored

: :

:

type class isa object = cname

: mtype : superc : sucient : pred : extent :

:

,

:

string type

,

,

, ;

,

,

set of class ,

, ,

boolean function set of object where: cname (c ) = nc the name of the class mtype (c ) = tE the member type of the class (tE 2 T ) superc (c ) = fc1; : : :; clg the set of immediate superclasses, ci 2 C (i = 1 : : : l) sucient (c ) = true=false the class has an all-/some-selector pred (c ) =(pc =? a boolean function pc 2 F (? if no predicate speci ed) supers if the class has an all-selector extent (c ) :=  supers if the class has a someT-selector extent (c ) j p (o ) g where supers := f o 2

;

i

ck superc (c ) 2

class Types : type some o: Objects where select [ tname(o) = tname(t) and o 6= t ] ( t: Types ) = fg and inherited (o )  Functions ; class Functions : function some o: Objects where select [ fname(o) = fname(f) and dom(o) 6= dom(f) and o 6= f ] ( f: Functions ) = fg and dom (o ) 2 Types and ran (o ) 2 Types ; class Classes : class some o: Objects where select [ cname(o) = cname(c) and o 6= c ] ( c: Classes ) = fg and mtype (o ) 2 Types and superc (o )  Classes ; class Views : class some c: Classes where select(c) = true ; 13

k

c i

Suggest Documents